diff options
Diffstat (limited to 'ldap/servers/plugins/replication')
78 files changed, 41769 insertions, 0 deletions
diff --git a/ldap/servers/plugins/replication/Makefile b/ldap/servers/plugins/replication/Makefile new file mode 100644 index 00000000..6f5341e8 --- /dev/null +++ b/ldap/servers/plugins/replication/Makefile @@ -0,0 +1,152 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server "Replication" plugin +# +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/replication-plugin +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk +include $(MCOM_ROOT)/ldapserver/ns_usedb.mk + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./replication.def +endif + +CFLAGS += $(SLCFLAGS) -DSLAPD_LOGGING + +ifeq ($(ARCH), WINNT) +CFLAGS += /WX +endif + +ifdef TEST_CL5 +CFLAGS += -DTEST_CL5 +endif + +INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(DB_INCLUDE) + +LOCAL_OBJS= \ + cl5_api.o \ + cl5_clcache.o \ + cl5_config.o \ + cl5_init.o \ + csnpl.o\ + legacy_consumer.o \ + llist.o\ + repl5_agmt.o \ + repl5_agmtlist.o \ + repl5_backoff.o \ + repl5_connection.o \ + repl5_inc_protocol.o \ + repl5_init.o\ + repl5_protocol.o \ + repl5_protocol_util.o \ + repl5_replica.o\ + repl5_replica_config.o\ + repl5_ruv.o\ + repl5_schedule.o \ + repl5_tot_protocol.o \ + repl5_total.o\ + repl5_mtnode_ext.o\ + repl5_plugins.o \ + repl_add.o \ + repl_bind.o \ + repl_compare.o \ + repl_connext.o \ + repl_controls.o \ + repl_delete.o \ + repl_entry.o \ + repl_ext.o \ + repl_extop.o \ + repl_globals.o \ + repl_init.o \ + repl_modify.o \ + repl_modrdn.o \ + repl_monitor.o \ + repl_objset.o \ + repl_opext.o \ + repl_ops.o \ + repl_rootdse.o \ + repl_search.o \ + replutil.o \ + urp.o \ + urp_glue.o \ + urp_tombstone.o \ + repl5_replica_hash.o\ + repl5_replica_dnhash.o\ + repl5_updatedn_list.o\ + +LIBREPLICATION_OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS)) + +ifeq ($(ARCH), WINNT) +REPLICATION_DLL_OBJ = $(addprefix $(OBJDEST)/, dllmain.o) +endif + +LIBREPLICATION= $(addprefix $(LIBDIR)/, $(REPLICATION_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAPLINK) $(DB_LIB) +endif + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(DB_LIB_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(DB_LIB) +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./replication.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBREPLICATION) + +$(LIBREPLICATION): $(LIBREPLICATION_OBJS) $(REPLICATION_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBREPLICATION_OBJS) $(REPLICATION_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) $(LDAP_LIBLDIF) $(NSPRLINK) + +tests: $(TEST_PROGS) + +veryclean: clean + +clean: + $(RM) $(LIBREPLICATION_OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(REPLICATION_DLL_OBJ) +endif + $(RM) $(LIBREPLICATION) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +# +# header file dependencies (incomplete) +# +$(LIBREPLICATION_OBJS): diff --git a/ldap/servers/plugins/replication/cl4.h b/ldap/servers/plugins/replication/cl4.h new file mode 100644 index 00000000..0dbcece2 --- /dev/null +++ b/ldap/servers/plugins/replication/cl4.h @@ -0,0 +1,65 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl4.h - global declarations used by the 4.0 style changelog module + */ + +#ifndef CL4_H +#define CL4_H + +#include "slapi-private.h" +#include "portable.h" /* GGOODREPL - is this cheating? */ + +#define CONFIG_CHANGELOG_SUFFIX_ATTRIBUTE "nsslapd-changelogsuffix" + +/* A place to store changelog config info */ +typedef struct _chglog4Info chglog4Info; + +/* in cl4.c */ +chglog4Info* changelog4_new (Slapi_Entry *e, char *errorbuf); +void changelog4_free (chglog4Info** cl4); +void changelog4_lock (Object *obj, PRBool write); +void changelog4_unlock (Object *obj); +const char * changelog4_get_dir (const chglog4Info* cl4); +const char * changelog4_get_suffix (const chglog4Info* cl4); +time_t changelog4_get_maxage (const chglog4Info* cl4); +unsigned long changelog4_get_maxentries (const chglog4Info* cl4); +void changelog4_set_dir (chglog4Info* cl4, const char *dir); +void changelog4_set_suffix (chglog4Info* cl4, const char *suffix); +void changelog4_set_maxage (chglog4Info* cl4, const char *maxage); +void changelog4_set_maxentries (chglog4Info* cl4, const char* maxentries); + +/* In cl4_suffix.c */ +char *get_changelog_dataversion(const chglog4Info* cl4); +void set_changelog_dataversion(chglog4Info* cl4, const char *dataversion); + +/* In cl4_config.c */ +int changelog4_config_init(); +void changelog4_config_destroy(); + +/* + * backend configuration information + * Previously, these two typedefs were in ../../slapd/slapi-plugin.h but + * the CL4 code is the only remaining code that references these definitions. + */ +typedef struct config_directive +{ + char *file_name; /* file from which to read directive */ + int lineno; /* line to read */ + int argc; /* number of argvs */ + char **argv; /* directive in agrv format */ +} slapi_config_directive; + +typedef struct be_config +{ + char *type; /* type of the backend */ + char *suffix; /* suffix of the backend */ + int is_private; /* 1 - private, 0 -not */ + int log_change; /* 1 - write change to the changelog; 0 - don't */ + slapi_config_directive *directives;/* configuration directives */ + int dir_count; /* number of directives */ +} slapi_be_config; + +#endif diff --git a/ldap/servers/plugins/replication/cl4_api.c b/ldap/servers/plugins/replication/cl4_api.c new file mode 100644 index 00000000..135b4a5b --- /dev/null +++ b/ldap/servers/plugins/replication/cl4_api.c @@ -0,0 +1,797 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl4_api.h - implementation of the minimal interface to 4.0 changelog necessary to + link 4.0 changelog to 5.0 replication + */ + +#include "repl.h" +#include "cl4_api.h" +#include "csnpl.h" +#include "cl4.h" + +/*** Data Structures ***/ + +/* changelog internal data */ +typedef struct cl4priv +{ + CSNPL *csnPL; /* csn pending list */ + int regID; /* csn function registration id */ +}CL4Private; + +/* callback data to get result of internal operations */ +typedef struct cl4ret +{ + int err; /* error code */ + Slapi_Entry *e; /* target entry */ +}CL4Ret; + +/* Global Data */ +static CL4Private s_cl4Desc; /* represents changelog state */ + +/*** Helper functions forward declarations ***/ +static int _cl4WriteOperation (const slapi_operation_parameters *op); +static void _cl4AssignCSNCallback (const CSN *csn, void *data); +static void _cl4AbortCSNCallback (const CSN *csn, void *data); +static char* _cl4MakeCSNDN (const CSN* csn); +static int _cl4GetEntry (const CSN *csn, Slapi_Entry **entry); +static void _cl4ResultCallback (int err, void *callback_data); +static int _cl4EntryCallback (Slapi_Entry *e, void *callback_data); +static PRBool _cl4CanAssignChangeNumber (const CSN *csn); +static int _cl4ResolveTargetDN (Slapi_Entry *entry, Slapi_DN **newTargetDN); +static int _cl4GetTargetEntry (Slapi_DN *targetDN, const char *uniqueid, Slapi_Entry **entry); +static int _cl4FindTargetDN (const CSN *csn, const char *uniqueid, + const Slapi_DN *targetSDN, Slapi_DN **newTargetDN); +static int _cl4AssignChangeNumber (changeNumber *cnum); +static int _cl4UpdateEntry (const CSN *csn, const char *changeType, const Slapi_DN *newTargetDN, changeNumber cnum); + +/*** API ***/ +int cl4Init () +{ + s_cl4Desc.csnPL = csnplNew (); + if (s_cl4Desc.csnPL == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4Init: failed to create CSN pending list\n"); + return CL4_CSNPL_ERROR; + } + + s_cl4Desc.regID = csnRegisterNewCSNCb(_cl4AssignCSNCallback, NULL, + _cl4AbortCSNCallback, NULL); + + return CL4_SUCCESS; +} + +void cl4Cleanup () +{ + if (s_cl4Desc.regID >= 0) + { + csnRemoveNewCSNCb(s_cl4Desc.regID); + s_cl4Desc.regID = -1; + } + + if (s_cl4Desc.csnPL == NULL) + csnplFree (&s_cl4Desc.csnPL); +} + +int cl4WriteOperation (const slapi_operation_parameters *op) +{ + int rc; + ReplicaId rd; + + if (op == NULL || !IsValidOperation (op)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4WriteEntry: invalid entry\n"); + return CL4_BAD_DATA; + } + + rc = _cl4WriteOperation (op); + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4WriteEntry: failed to write changelog entry\n"); + return rc; + } + + /* the entry is generated by this server - remove the entry from the pending list */ + rd= csn_get_replicaid(op->csn); + if (rd == slapi_get_replicaid ()) + { + rc = csnplRemove (s_cl4Desc.csnPL, op->csn); + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "cl4WriteEntry: failed to remove CSN from the pending list\n"); + rc = CL4_CSNPL_ERROR; + } + } + + return rc; +} + +int cl4ChangeTargetDN (const CSN *csn, const char *newDN) +{ + Slapi_PBlock *pb; + char *changeEntryDN; + Slapi_Mods smods; + int res; + + if (csn == NULL || newDN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4ChangeTargetDN: invalid argument\n"); + return CL4_BAD_DATA; + } + + /* construct dn of the change entry */ + changeEntryDN = _cl4MakeCSNDN (csn); + if (changeEntryDN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "cl4ChangeTargetDN: failed to construct change entry dn\n"); + return CL4_MEMORY_ERROR; + } + + pb = slapi_pblock_new (); + + slapi_mods_init(&smods, 1); + slapi_mods_add(&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_targetdn, + strlen (newDN), newDN); + slapi_modify_internal_set_pb(pb, changeEntryDN, slapi_mods_get_ldapmods_byref(&smods), + NULL, NULL, repl_get_plugin_identity(PLUGIN_LEGACY_REPLICATION), 0); + slapi_modify_internal_pb (pb); + + slapi_mods_done(&smods); + slapi_ch_free ((void**)&changeEntryDN); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + slapi_pblock_destroy(pb); + + if (res != LDAP_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "cl4ChangeTargetDN: an error occured while modifying change entry with csn %s: %s. " + "Logging of changes is disabled.\n", csn_as_string(csn,PR_FALSE,s), ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + return CL4_LDAP_ERROR; + } + + return CL4_SUCCESS; +} + +void cl4AssignChangeNumbers (time_t when, void *arg) +{ + int rc = CL4_SUCCESS; + Slapi_Entry *entry; + CSN *csn = NULL; + Slapi_DN *newTargetDN; + changeNumber cnum; + char *changetype; + + /* we are looping though the entries ready to be commited updating there target dn + and assigning change numbers */ + while (_cl4GetEntry (csn, &entry) == CL4_SUCCESS) + { + /* ONREPL - I think we need to free previous csn */ + csn = csn_new_by_string(slapi_entry_attr_get_charptr (entry, attr_csn)); + /* all conflicts involving this entry have been resolved */ + if (_cl4CanAssignChangeNumber (csn)) + { + /* figure out the name of the target entry that corresponds to change csn */ + rc = _cl4ResolveTargetDN (entry, &newTargetDN); + slapi_entry_free (entry); + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "cl4AssignChangeNumbers: failed to resolve target dn\n"); + break; + } + + _cl4AssignChangeNumber (&cnum); + + changetype = slapi_entry_attr_get_charptr (entry, attr_changetype); + + /* update change entry: write change number and remove csn attribute. + Note that we leave uniqueid in the entry to avoid an extra update. + This is ok since uniqueid is an operational attribute not returned + to the client by default. */ + rc = _cl4UpdateEntry (csn, changetype, newTargetDN, cnum); + if (newTargetDN) + { + slapi_sdn_free (&newTargetDN); + } + + slapi_ch_free ((void**)&changetype); + + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "cl4AssignChangeNumbers: failed to update changelog entry\n"); + break; + } + } + else /* went too far */ + { + slapi_entry_free (entry); + break; + } + } +} + + +/*** Helper Functions ***/ + +/* adds new change record to 4.0 changelog */ +static int _cl4WriteOperation (const slapi_operation_parameters *op) +{ + int rc = CL4_SUCCESS, res; + char *changeEntryDN, *timeStr; + Slapi_Entry *e; + Slapi_PBlock *pb = NULL; + Slapi_Value *values[3]; + char s[CSN_STRSIZE]; + + slapi_log_error (SLAPI_LOG_PLUGIN, repl_plugin_name, + "_cl4WriteEntry: writing change record with csn %s for dn: \"%s\"\n", + csn_as_string(op->csn,PR_FALSE,s), op->target_address.dn); + + /* create change entry dn */ + changeEntryDN = _cl4MakeCSNDN (op->csn); + if (changeEntryDN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4WriteEntry: failed to create entry dn\n"); + return CL4_MEMORY_ERROR; + } + + /* + * Create the entry struct, and fill in fields common to all types + * of change records. + */ + e = slapi_entry_alloc(); + if (e == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4WriteEntry: failed to allocate change entry\n"); + return CL4_MEMORY_ERROR; + } + + slapi_entry_set_dn(e, slapi_ch_strdup (changeEntryDN)); + + /* Set the objectclass attribute */ + values [0] = slapi_value_new (NULL); + values [1] = slapi_value_new (NULL); + values [2] = NULL; + slapi_value_set_string(values[0], "top"); + slapi_value_set_string(values[1], "changelogentry"); + slapi_entry_add_values_sv (e, "objectclass", values); + + /* ONREPL - for now we have to free Slapi_Values since api makes copy; + this will change when a new set of api is added */ + slapi_value_free (&(values[0])); + slapi_value_free (&(values[1])); + + /* Set the changeNumber attribute */ + /* Need to set this because it is required by schema */ + slapi_entry_attr_set_charptr (e, attr_changenumber, "0"); + + /* Set the targetentrydn attribute */ + if (op->operation_type == SLAPI_OPERATION_ADD) /* use raw dn */ + slapi_entry_attr_set_charptr (e, attr_targetdn, slapi_entry_get_dn (op->p.p_add.target_entry)); + else /* use normolized dn */ + slapi_entry_attr_set_charptr (e, attr_targetdn, op->target_address.dn); + + /* ONREPL - set dbid attribute */ + + /* Set the changeTime attribute */ + timeStr = format_localTime (current_time()); + slapi_entry_attr_set_charptr (e, attr_changetime, timeStr); + slapi_ch_free((void**)&timeStr); + + /* + * Finish constructing the entry. How to do it depends on the type + * of modification being logged. + */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (entry2reple(e, op->p.p_add.target_entry) != 0 ) + { + rc = CL4_INTERNAL_ERROR; + goto done; + } + + break; + + case SLAPI_OPERATION_MODIFY: if (mods2reple(e, op->p.p_modify.modify_mods) != 0) + { + rc = CL4_INTERNAL_ERROR; + goto done; + } + + break; + + case SLAPI_OPERATION_MODDN: if (modrdn2reple(e, op->p.p_modrdn.modrdn_newrdn, + op->p.p_modrdn.modrdn_deloldrdn, op->p.p_modrdn.modrdn_mods) != 0) + { + rc = CL4_INTERNAL_ERROR; + goto done; + } + + break; + + case SLAPI_OPERATION_DELETE: /* Set the changetype attribute */ + slapi_entry_attr_set_charptr (e, attr_changetype, "delete"); + break; + } + + pb = slapi_pblock_new (pb); + slapi_add_entry_internal_set_pb (pb, e, NULL, repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_add_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + slapi_pblock_destroy(pb); + + if (res != LDAP_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4WriteEntry: an error occured while adding change entry with csn %s, dn = %s: %s. " + "Logging of changes is disabled.\n", csn_as_string(op->csn,PR_FALSE,s), op->target_address.dn, + ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + rc = CL4_LDAP_ERROR; + } + +done: + if (changeEntryDN) + slapi_ch_free((void **) &changeEntryDN); + + return rc; +} + +static void _cl4AssignCSNCallback (const CSN *csn, void *data) +{ + int rc; + + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4AssignCSNCallback: null csn\n"); + return; + } + + rc = csnplInsert (s_cl4Desc.csnPL, csn); + + if (rc == -1) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4AssignCSNCallback: failed to insert csn %s to the pending list\n", + csn_as_string(csn,PR_FALSE,s)); + } +} + +static void _cl4AbortCSNCallback (const CSN *csn, void *data) +{ + int rc; + + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4AbortCSNCallback: null csn\n"); + return; + } + + rc = csnplRemove (s_cl4Desc.csnPL, csn); + if (rc == -1) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4AbortCSNCallback: failed to remove csn %s from the pending list\n", + csn_as_string(csn,PR_FALSE,s)); + } +} + +/* initial dn format: csn=<csn>,<changelog suffix>. For instance, csn=013744022939465,cn=changelog4 */ +static char* _cl4MakeCSNDN (const CSN* csn) +{ + char *pat, *edn; + char *suffix = changelog4_get_suffix (); + char s[CSN_STRSIZE]; + + if (suffix == NULL) + return NULL; + + /* Construct the dn of this change record */ + pat = "%s=%s,%s"; + edn = slapi_ch_malloc(strlen(pat) + strlen(attr_csn) + strlen(suffix) + CSN_STRSIZE + 1); + if (edn) + sprintf(edn, pat, attr_csn, csn_as_string(csn,PR_FALSE,s), suffix); + slapi_ch_free ((void **)&suffix); + + return edn; +} + +static int _cl4GetEntry (const CSN *csn, Slapi_Entry **entry) +{ + int rc; + char *suffix = changelog4_get_suffix (); + int type; + const char *value; + CL4Ret ret; + char s[CSN_STRSIZE]; + + if (csn == NULL) /* entry with smallest csn */ + { + type = SLAPI_SEQ_FIRST; + value = NULL; + } + else /* entry with next csn */ + { + type = SLAPI_SEQ_NEXT; + value = csn_as_string(csn,PR_FALSE,s); + } + + rc = slapi_seq_callback(suffix, type, attr_csn, (char*)value, NULL, 0, &ret, NULL, + _cl4ResultCallback, _cl4EntryCallback, NULL); + slapi_ch_free ((void**)&suffix); + + if (rc != 0 || ret.err != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetEntry: failed to get next changelog entry\n"); + return CL4_INTERNAL_ERROR; + } + + *entry = ret.e; + return CL4_SUCCESS; +} + +static void _cl4ResultCallback (int err, void *callback_data) +{ + CL4Ret *ret = (CL4Ret *)callback_data; + + if (ret) + { + ret->err = err; + } +} + +static int _cl4EntryCallback (Slapi_Entry *e, void *callback_data) +{ + CL4Ret *ret = (CL4Ret *)callback_data; + + if (ret) + { + ret->e = slapi_entry_dup (e); + } + + return 0; +} + +static PRBool _cl4CanAssignChangeNumber (const CSN *csn) +{ + CSN *commitCSN = NULL; + + /* th CSN is withtin region that can be commited */ + if (csn && csn_compare(csn, commitCSN) < 0) + return PR_TRUE; + + return PR_FALSE; +} + +/* ONREPL - describe algorithm */ +static int _cl4ResolveTargetDN (Slapi_Entry *entry, Slapi_DN **newTargetDN) +{ + int rc; + char *csnStr = slapi_entry_attr_get_charptr (entry, attr_csn); + char *targetdn = slapi_entry_attr_get_charptr (entry, attr_targetdn); + const char *uniqueid = slapi_entry_get_uniqueid (entry); + char *changetype = slapi_entry_attr_get_charptr (entry, attr_changetype); + CSN *csn = csn_new_by_string (csnStr); + Slapi_Entry *targetEntry = NULL; + const Slapi_DN *teSDN; + Slapi_DN *targetSDN; + const CSN *teDNCSN = NULL; + + *newTargetDN = NULL; + + targetSDN = slapi_sdn_new(); + if (strcasecmp (changetype, "add") == 0) /* this is add operation - we have rawdn */ + slapi_sdn_set_dn_byref (targetSDN, targetdn); + else + slapi_sdn_set_ndn_byref (targetSDN, targetdn); + + /* read the entry to which the change was applied */ + rc = _cl4GetTargetEntry (targetSDN, uniqueid, &targetEntry); + if (rc != CL4_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4ResolveTargetDN: failed to get target entry\n"); + goto done; + } + + teDNCSN = entry_get_dncsn(targetEntry); + if (teDNCSN == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4ResolveTargetDN: failed to get target entry dn\n"); + rc = CL4_BAD_FORMAT; + goto done; + } + + if (csn_compare(teDNCSN, csn) <= 0) + { + /* the change entry target dn should be the same as target entry dn */ + teSDN = slapi_entry_get_sdn_const(targetEntry); + + /* target dn of change entry is not the same as dn of the target entry - update */ + if (slapi_sdn_compare (teSDN, targetSDN) != 0) + { + *newTargetDN = slapi_sdn_dup (targetSDN); + } + } + else /* the target entry was renamed since this change occur - find the right target dn */ + { + rc = _cl4FindTargetDN (csn, uniqueid, targetSDN, newTargetDN); + } + +done:; + if (csnStr) + slapi_ch_free ((void**)&csnStr); + + if (targetdn) + slapi_ch_free ((void**)&targetdn); + + if (uniqueid) + slapi_ch_free ((void**)&uniqueid); + + if (changetype) + slapi_ch_free ((void**)&changetype); + + if (targetEntry) + slapi_entry_free (targetEntry); + + if (targetSDN) + slapi_sdn_free (&targetSDN); + + return rc; +} + +static int _cl4GetTargetEntry (Slapi_DN *sdn, const char *uniqueid, Slapi_Entry **entry) +{ + Slapi_PBlock *pb; + char filter [128]; + int res, rc = CL4_SUCCESS; + Slapi_Entry **entries = NULL; + + /* read corresponding database entry based on its uniqueid */ + sprintf (filter, "uniqueid=%s", uniqueid); + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, (char*)slapi_sdn_get_ndn(sdn), LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_search_internal_pb (pb); + + if (pb == NULL) + { + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (res == LDAP_NO_SUCH_OBJECT) /* entry not found */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetTargetEntry: entry (%s) not found\n", + slapi_sdn_get_ndn(sdn)); + rc = CL4_NOT_FOUND; + goto done; + } + + if (res != LDAP_SUCCESS) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4ResolveTargetDN: an error occured while searching for directory entry with uniqueid %s: %s. " + "Logging of changes is disabled.\n", uniqueid, ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (entries == NULL || entries [0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4GetTargetEntry: entry (%s) not found\n", + slapi_sdn_get_ndn(sdn)); + rc = CL4_NOT_FOUND; + goto done; + } + + *entry = slapi_entry_dup (entries[0]); + +done: + if (pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + } + + return rc; +} + +static int _cl4FindTargetDN (const CSN *csn, const char *uniqueid, + const Slapi_DN *targetSDN, Slapi_DN **newTargetDN) +{ + int rc = CL4_SUCCESS; + int res, i; + Slapi_PBlock *pb; + char *suffix = changelog4_get_suffix (); + char filter [128]; + Slapi_Entry **entries; + int minIndex = 0; + CSN *minCSN = NULL, *curCSN; + char *curType; + const Slapi_DN *sdn; + char s[CSN_STRSIZE]; + + *newTargetDN = NULL; + + /* Look for all modifications to the target entry with csn larger than + this csn. We are only interested in rename operations, but change type + is currently not indexed */ + sprintf (filter, "&(uniqueid=%s)(csn>%s)", uniqueid, csn_as_string(csn,PR_FALSE,s)); + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, suffix, LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_ch_free ((void**)&suffix); + if (pb == NULL) + { + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (res == LDAP_NO_SUCH_OBJECT) /* entry not found */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4FindTargetDN: no entries much filter (%s)\n", + filter); + rc = CL4_NOT_FOUND; + goto done; + } + + if (res != LDAP_SUCCESS) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "_cl4ResolveTargetDN: an error occured while searching change entries matching filter %s: %s. " + "Logging of changes is disabled.\n", filter, ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + rc = CL4_LDAP_ERROR; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (entries == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4FindTargetDN: no entries much filter (%s)\n", + filter); + rc = CL4_NOT_FOUND; + goto done; + } + + i = 0; + + /* find rename operation with smallest csn - its target dn should be the name + of our change entry */ + while (entries[i]) + { + curType = slapi_entry_attr_get_charptr (entries[i], attr_changetype); + if (curType && strcasecmp (curType, "modrdn") == 0) + { + curCSN = csn_new_by_string (slapi_entry_attr_get_charptr (entries[i], attr_csn)); + if (minCSN == NULL || csn_compare (curCSN, minCSN) < 0) + { + minCSN = curCSN; + minIndex = i; + } + } + + if (curType) + slapi_ch_free ((void**)&curType); + + i ++; + } + + if (curCSN == NULL) + { + rc = CL4_NOT_FOUND; + goto done; + } + + /* update targetDN of our entry if necessary */ + sdn = slapi_entry_get_sdn_const(entries[minIndex]); + + /* target dn does not match to renaming operation - rename change entry */ + if (slapi_sdn_compare (sdn, targetSDN) != 0) + *newTargetDN = slapi_sdn_dup (sdn); + +done: + if (pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + } + + return rc; +} + +static int _cl4AssignChangeNumber (changeNumber *cnum) +{ + *cnum = ldapi_assign_changenumber(); + return CL4_SUCCESS; +} + +static int _cl4UpdateEntry (const CSN *csn, const char *changeType, + const Slapi_DN *newDN, changeNumber cnum) +{ + Slapi_PBlock *pb; + char *dn; + const char *dnTemp; + int res; + Slapi_Mods smods; + char cnumbuf[32]; + + if (csn == NULL || changeType == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4UpdateEntry: invalid argument\n"); + return CL4_BAD_DATA; + } + + dn = _cl4MakeCSNDN (csn); + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_cl4UpdateEntry: failed to create entry dn\n"); + return CL4_MEMORY_ERROR; + } + + slapi_mods_init(&smods, 2); + if (newDN) + { + if (strcasecmp (changeType, "add") == 0) + dnTemp = slapi_sdn_get_dn (newDN); + else + dnTemp = slapi_sdn_get_ndn (newDN); + + slapi_mods_add(&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_targetdn, + strlen (dnTemp), dnTemp); + } + /* Set the changeNumber attribute */ + sprintf(cnumbuf, "%lu", cnum); + slapi_mods_add (&smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, attr_changenumber, + strlen (cnumbuf), cnumbuf); + pb = slapi_pblock_new (); + slapi_modify_internal_set_pb (pb, dn, slapi_mods_get_ldapmods_byref(&smods), NULL, NULL, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), 0); + slapi_modify_internal_pb (pb); + slapi_mods_done(&smods); + slapi_ch_free ((void**)&dn); + + if (pb == NULL) + { + return CL4_LDAP_ERROR; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + slapi_pblock_destroy(pb); + if (res != LDAP_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "cl4ChangeTargetDN: an error occured while modifying change entry with csn %s: %s. " + "Logging of changes is disabled.\n", csn_as_string(csn,PR_FALSE,s), ldap_err2string(res)); + /* GGOODREPL g_set_repl_backend( NULL ); */ + return CL4_LDAP_ERROR; + } + + if ( ldapi_get_first_changenumber() == (changeNumber) 0L ) + { + ldapi_set_first_changenumber( cnum ); + } + + ldapi_commit_changenumber(cnum); + return CL4_SUCCESS; +} diff --git a/ldap/servers/plugins/replication/cl4_api.h b/ldap/servers/plugins/replication/cl4_api.h new file mode 100644 index 00000000..c0b20e57 --- /dev/null +++ b/ldap/servers/plugins/replication/cl4_api.h @@ -0,0 +1,67 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl4_api.h - minimal interface to 4.0 changelog necessary to link 4.0 changelog + to 5.0 replication + */ + +#ifndef CL4_API_H +#define CL4_API_H + +#include "repl.h" + +/*** Error Codes ***/ +enum +{ + CL4_SUCCESS, + CL4_BAD_DATA, + CL4_BAD_FORMAT, + CL4_NOT_FOUND, + CL4_MEMORY_ERROR, + CL4_CSNPL_ERROR, + CL4_LDAP_ERROR, + CL4_INTERNAL_ERROR +}; + +/*** APIs ***/ +/* Name: cl4Init + Description: initializes 4.0 changelog subsystem + Parameters: none + Return: ???? + */ +int cl4Init (); + +/* Name: cl4WriteOperation + Description: logs operation to 4.0 changelog; operation must go through CD&R engine first + Parameters: op - operation to be logged + + Return: ???? + */ +int cl4WriteOperation (const slapi_operation_parameters *op); + +/* Name: cl4ChangeTargetDN + Description: modifies change entry target dn; should be called for conflicts due to naming collisions; + raw dn should be passed for add operations; normolized dn otherwise. + Parameters: csn - csn of the change entry to be modified + newDN - new target dn of the entry + Return: ???? + */ +int cl4ChangeTargetDN (const CSN* csn, const char *newDN); + +/* Name: cl4AssignChangeNumbers + Description: this function should be called periodically to assign change numbers to changelog + entries. Intended for use with event queue + Parameters: parameters are not currently used + Return: none + */ +void cl4AssignChangeNumbers (time_t when, void *arg); + +/* Name: cl4Cleanup + Description: frees memory held by 4.0 changelog subsystem + Parameters: none + Return: none + */ +void cl4Clean (); +#endif diff --git a/ldap/servers/plugins/replication/cl4_init.c b/ldap/servers/plugins/replication/cl4_init.c new file mode 100644 index 00000000..6c12f0b0 --- /dev/null +++ b/ldap/servers/plugins/replication/cl4_init.c @@ -0,0 +1,349 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* cl4_init.c - implments initialization/cleanup functions for + 4.0 style changelog + */ + +#include <string.h> + +#include "slapi-plugin.h" +#include "cl4.h" +#include "repl.h" + +/* forward declarations */ +static int changelog4_create_be(); +static int changelog4_start_be (); +static int changelog4_close(); +static int changelog4_remove(); + +/* + * Initialise the 4.0 Changelog + */ +int changelog4_init () +{ + int rc= 0; /* OK */ + Slapi_Backend *rbe; + changeNumber first_change = 0UL, last_change = 0UL; + int lderr; + + if (changelog4_create_be() < 0 ) + { + rc= -1; + } + else + { + rc = changelog4_start_be (); + } + + if(rc == 0) + { + rbe = get_repl_backend(); + if(rbe!=NULL) + { + /* We have a Change Log. Check it's valid. */ + /* changelog has to be started before its + data version can be read */ + const char *sdv= get_server_dataversion(); + const char *cdv= get_changelog_dataversion(); + char *suffix = changelog4_get_suffix (); + if(!cdv || strcmp(sdv,cdv)!=0) + { + + /* The SDV and CDV are not the same. The Change Log is invalid. + It must be removed. */ + /* ONREPL - currently we go through this code when the changelog + is first created because we can't tell new backend from the + existing one.*/ + rc = changelog4_close(); + rc = changelog4_remove(); + + /* now restart the changelog */ + changelog4_start_be (); + + create_entity( suffix, "extensibleobject"); + /* Write the Server Data Version onto the changelog suffix entry */ + /* JCMREPL - And the changelog database version number */ + set_changelog_dataversion(sdv); + slapi_ch_free ((void **)&suffix); + + } + } + } + + if(rc != 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, + "An error occurred configuring the changelog database\n" ); + } + + first_change = replog_get_firstchangenum( &lderr ); + last_change = replog_get_lastchangenum( &lderr ); + ldapi_initialize_changenumbers( first_change, last_change ); + + return rc; +} + +static int + +changelog4_close() +{ + int rc= 0 /* OK */; + Slapi_Backend *rbe= get_repl_backend(); + Slapi_PBlock *pb = slapi_pblock_new (); + IFP closefn = NULL; + + rc = slapi_be_getentrypoint (rbe, SLAPI_PLUGIN_CLOSE_FN, (void**)&closefn, pb); + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: backend close entry point is missing. " + "Replication subsystem disabled.\n"); + slapi_pblock_destroy (pb); + set_repl_backend( NULL ); + return -1; + } + + rc = closefn (pb); + + if (rc != 0) + { + + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, "Error: the changelog database could " + "not be closed. Replication subsystem disabled.\n"); + set_repl_backend( NULL ); + rc = -1; + } + + slapi_pblock_destroy (pb); + return rc; + +} + +static int +changelog4_remove() +{ + int rc= 0 /* OK */; + Slapi_Backend *rbe= get_repl_backend(); + Slapi_PBlock *pb = slapi_pblock_new (); + IFP rmdbfn = NULL; + + rc = slapi_be_getentrypoint (rbe, SLAPI_PLUGIN_DB_RMDB_FN, (void**)&rmdbfn, pb); + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: backend rmdb entry point is missing. " + "Replication subsystem disabled.\n"); + slapi_pblock_destroy (pb); + set_repl_backend( NULL ); + return -1; + } + + rc = rmdbfn (pb); + + if (rc != 0) + { + + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, "Error: the changelog database could " + "not be removed. Replication subsystem disabled.\n"); + rc = -1; + } + else + { + slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name, "New database generation computed. " + "Changelog database removed.\n"); + } + + slapi_pblock_destroy (pb); + return rc; +} + +static Slapi_Backend *repl_backend = NULL; + +Slapi_Backend +*get_repl_backend() +{ + return repl_backend; +} + +void +set_repl_backend(Slapi_Backend *be) +{ + repl_backend = be; +} + + +int changelog4_shutdown () +{ + /* ONREPL - will shutdown the backend */ + int rc = 1; + + return rc; +} + +static void changelog4_init_trimming () +{ + char *cl_maxage = changelog4_get_maxage (); + unsigned long cl_maxentries = changelog4_get_maxentries (); + time_t ageval = age_str2time (cl_maxage); + + slapi_ch_free ((void **)&cl_maxage); + + init_changelog_trimming(cl_maxentries, ageval ); +} + + + +/* + * Function: changelog4_create_be + * Arguments: none + * Returns: 0 on success, non-0 on error + * Description: configures changelog backend instance. + */ + +static int +changelog4_create_be() +{ + int i, dir_count = 5; + Slapi_Backend *rbe; + slapi_be_config config; + char *cl_dir = changelog4_get_dir (); + char *cl_suffix; + + if ( cl_dir == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: no directory specified for changelog database.\n"); + return -1; + } + + cl_suffix = changelog4_get_suffix (); + + if ( cl_suffix == NULL ) { + slapi_ch_free ((void **)&cl_dir); + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: no suffix specified for changelog database.\n"); + return -1; + } + + /* setup configuration parameters for backend initialization */ + config.type = CHANGELOG_LDBM_TYPE; + config.suffix = cl_suffix; + config.is_private = 1; /* yes */ + config.log_change = 0; /* no */ + config.directives = (slapi_config_directive*)slapi_ch_calloc( + dir_count, sizeof(slapi_config_directive)); + config.dir_count = dir_count; + + for (i = 0; i < dir_count; i++) + { + config.directives[i].file_name = "(internal)"; + config.directives[i].lineno = 0; + } + + /* setup indexes */ + config.directives[0].argv = NULL; + config.directives[0].argc = 3; + charray_add( &(config.directives[0].argv), slapi_ch_strdup( "index" )); + charray_add( &(config.directives[0].argv), slapi_ch_strdup( attr_changenumber )); + charray_add( &(config.directives[0].argv), slapi_ch_strdup( "eq" )); + + /* Set up the database directory */ + config.directives[1].argv = NULL; + config.directives[1].argc = 2; + charray_add( &(config.directives[1].argv), slapi_ch_strdup( "directory" )); + charray_add( &(config.directives[1].argv), slapi_ch_strdup( cl_dir )); + + /* Override the entry cache size */ + config.directives[2].argv = NULL; + config.directives[2].argc = 2; + charray_add( &(config.directives[2].argv), slapi_ch_strdup( "cachesize" )); + charray_add( &(config.directives[2].argv), slapi_ch_strdup( "10" )); + + /* Override the database cache size */ + config.directives[3].argv = NULL; + config.directives[3].argc = 2; + charray_add( &(config.directives[3].argv), slapi_ch_strdup( "dbcachesize" )); + charray_add( &(config.directives[3].argv), slapi_ch_strdup( "1000000" )); + + /* Override the allids threshold */ + config.directives[4].argv = NULL; + config.directives[4].argc = 2; + charray_add( &(config.directives[4].argv), slapi_ch_strdup( "allidsthreshold" )); + /* assumes sizeof(int) >= 32 bits */ + charray_add( &(config.directives[4].argv), slapi_ch_strdup( "2147483647" )); + + /* rbe = slapi_be_create_instance(&config, LDBM_TYPE); */ + rbe= NULL; + + /* free memory allocated to argv */ + for (i = 0; i < dir_count; i++) + { + charray_free (config.directives[i].argv); + } + + slapi_ch_free ((void **)&config.directives); + slapi_ch_free ((void **)&cl_dir); + slapi_ch_free ((void **)&cl_suffix); + + if (rbe == NULL) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: failed to create changelog backend. " + "Replication disabled.\n"); + return -1; + } + + set_repl_backend (rbe); + + changelog4_init_trimming (); + + return 0; +} + +/* Name: changelog4_start_be + * Parameters: none + * Return: 0 if successful, non 0 otherwise + * Description: starts the changelog backend; backend must be configured + * first via call to changelog4_create_be + */ +static int +changelog4_start_be () +{ + int rc; + IFP startfn = NULL; + Slapi_PBlock *pb; + Slapi_Backend *rbe = get_repl_backend (); + + if (rbe) + { + pb = slapi_pblock_new(); + rc = slapi_be_getentrypoint(rbe, SLAPI_PLUGIN_START_FN, (void**)&startfn, pb); + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: backend start entry point is missing. " + "Replication subsystem disabled.\n"); + slapi_pblock_destroy (pb); + set_repl_backend( NULL ); + return -1; + } + + rc = startfn (pb); + slapi_pblock_destroy (pb); + + if (rc != 0) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Error: Failed to start changelog backend. " + "Replication subsystem disabled.\n"); + set_repl_backend( NULL ); + return -1; + } + } + + return 0; +} + diff --git a/ldap/servers/plugins/replication/cl5.h b/ldap/servers/plugins/replication/cl5.h new file mode 100644 index 00000000..a80c666b --- /dev/null +++ b/ldap/servers/plugins/replication/cl5.h @@ -0,0 +1,38 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5.h - changelog related function */ + +#ifndef CL5_H +#define CL5_H + +#include "cl5_api.h" /* changelog access APIs */ + +typedef struct changelog5Config +{ + char *dir; +/* These 2 parameters are needed for changelog trimming. Already present in 5.0 */ + char *maxAge; + int maxEntries; +/* the changelog DB configuration parameters are defined as CL5DBConfig in cl5_api.h */ + CL5DBConfig dbconfig; +}changelog5Config; + +/* initializes changelog*/ +int changelog5_init(); +/* cleanups changelog data */ +void changelog5_cleanup(); +/* initializes changelog configurationd */ +int changelog5_config_init(); +/* cleanups config data */ +void changelog5_config_cleanup(); +/* reads changelog configuration */ +int changelog5_read_config (changelog5Config *config); +/* cleanups the content of the config structure */ +void changelog5_config_done (changelog5Config *config); +/* frees the content and the config structure */ +void changelog5_config_free (changelog5Config **config); + +#endif diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c new file mode 100644 index 00000000..792d3646 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_api.c @@ -0,0 +1,6512 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* cl5_api.c - implementation of 5.0 style changelog API */ + +#include <errno.h> +#include <sys/stat.h> +#if defined( OS_solaris ) || defined( hpux ) +#include <sys/types.h> +#include <sys/statvfs.h> +#endif +#if defined( linux ) +#include <sys/vfs.h> +#endif + + +#include "cl5_api.h" +#include "plhash.h" + +#include "db.h" +#include "cl5_clcache.h" /* To use the Changelog Cache */ +#include "repl5.h" /* for agmt_get_consumer_rid() */ + +#define CL5_TYPE "Changelog5" /* changelog type */ +#define VERSION_SIZE 127 /* size of the buffer to hold changelog version */ +#define GUARDIAN_FILE "guardian" /* name of the guardian file */ +#define VERSION_FILE "DBVERSION" /* name of the version file */ +#define MAX_TRIALS 50 /* number of retries on db operations */ +#define V_5 5 /* changelog entry version */ +#define CHUNK_SIZE 64*1024 +#define DBID_SIZE 64 +#define FILE_SEP "_" /* separates parts of the db file name */ + +#define T_CSNSTR "csn" +#define T_UNIQUEIDSTR "nsuniqueid" +#define T_PARENTIDSTR "parentuniqueid" +#define T_NEWSUPERIORDNSTR "newsuperiordn" +#define T_NEWSUPERIORIDSTR "newsuperioruniqueid" +#define T_REPLGEN "replgen" + +#define ENTRY_COUNT_TIME 111 /* this time is used to construct csn + used to store/retrieve entry count */ +#define PURGE_RUV_TIME 222 /* this time is used to construct csn + used to store purge RUV vector */ +#define MAX_RUV_TIME 333 /* this time is used to construct csn + used to store upper boundary RUV vector */ + +#define DB_EXTENSION_DB3 "db3" +#define DB_EXTENSION "db4" + +#define HASH_BACKETS_COUNT 16 /* number of buckets in a hash table */ + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4100 +#define DEFAULT_DB_OP_FLAGS DB_AUTO_COMMIT +#define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval) \ +{ \ + if (((oflags) & DB_INIT_TXN) && ((oflags) & DB_INIT_LOG)) \ + { \ + (rval) = (db)->open((db), (txnid), (file), (database), (type), (flags)|DB_AUTO_COMMIT, (mode)); \ + } \ + else \ + { \ + (rval) = (db)->open((db), (txnid), (file), (database), (type), (flags), (mode)); \ + } \ +} +#else /* older then db 41 */ +#define DEFAULT_DB_OP_FLAGS 0 +#define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval) \ + (rval) = (db)->open((db), (file), (database), (type), (flags), (mode)) +#endif + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4000 +#define DB_ENV_SET_REGION_INIT(env) (env)->set_flags((env), DB_REGION_INIT, 1) +#define DB_ENV_SET_TAS_SPINS(env, tas_spins) \ + (env)->set_tas_spins((env), (tas_spins)) +#define TXN_BEGIN(env, parent_txn, tid, flags) \ + (env)->txn_begin((env), (parent_txn), (tid), (flags)) +#define TXN_COMMIT(txn, flags) (txn)->commit((txn), (flags)) +#define TXN_ABORT(txn) (txn)->abort(txn) +#define TXN_CHECKPOINT(env, kbyte, min, flags) \ + (env)->txn_checkpoint((env), (kbyte), (min), (flags)) +#define MEMP_STAT(env, gsp, fsp, flags, malloc) \ + (env)->memp_stat((env), (gsp), (fsp), (flags)) +#define MEMP_TRICKLE(env, pct, nwrotep) \ + (env)->memp_trickle((env), (pct), (nwrotep)) +#define LOG_ARCHIVE(env, listp, flags, malloc) \ + (env)->log_archive((env), (listp), (flags)) +#define LOG_FLUSH(env, lsn) (env)->log_flush((env), (lsn)) +#define LOCK_DETECT(env, flags, atype, aborted) \ + (env)->lock_detect((env), (flags), (atype), (aborted)) + +#else /* older than db 4.0 */ +#define DB_ENV_SET_REGION_INIT(env) db_env_set_region_init(1) +#define DB_ENV_SET_TAS_SPINS(env, tas_spins) \ + db_env_set_tas_spins((tas_spins)) +#define TXN_BEGIN(env, parent_txn, tid, flags) \ + txn_begin((env), (parent_txn), (tid), (flags)) +#define TXN_COMMIT(txn, flags) txn_commit((txn), (flags)) +#define TXN_ABORT(txn) txn_abort((txn)) +#define TXN_CHECKPOINT(env, kbyte, min, flags) \ + txn_checkpoint((env), (kbyte), (min), (flags)) +#define MEMP_TRICKLE(env, pct, nwrotep) memp_trickle((env), (pct), (nwrotep)) +#define LOG_FLUSH(env, lsn) log_flush((env), (lsn)) +#define LOCK_DETECT(env, flags, atype, aborted) \ + lock_detect((env), (flags), (atype), (aborted)) + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300 +#define MEMP_STAT(env, gsp, fsp, flags, malloc) memp_stat((env), (gsp), (fsp)) +#define LOG_ARCHIVE(env, listp, flags, malloc) \ + log_archive((env), (listp), (flags)) + +#else /* older than db 3.3 */ +#define MEMP_STAT(env, gsp, fsp, flags, malloc) \ + memp_stat((env), (gsp), (fsp), (malloc)) +#define LOG_ARCHIVE(env, listp, flags, malloc) \ + log_archive((env), (listp), (flags), (malloc)) +#endif +#endif +/* + * The defult thread stacksize for nspr21 is 64k. For OSF, we require + * a larger stacksize as actual storage allocation is higher i.e + * pointers are allocated 8 bytes but lower 4 bytes are used. + * The value 0 means use the default stacksize. + */ +#if defined (OSF1) || defined (__LP64__) || defined (_LP64) /* 64-bit architectures need bigger stacks */ +#define DEFAULT_THREAD_STACKSIZE 131072L +#else +#define DEFAULT_THREAD_STACKSIZE 0 +#endif + +#ifdef _WIN32 +#define FILE_CREATE_MODE S_IREAD | S_IWRITE +#define DIR_CREATE_MODE 0755 +#else /* _WIN32 */ +#define FILE_CREATE_MODE S_IRUSR | S_IWUSR +#define DIR_CREATE_MODE 0755 +#endif + +#define NO_DISK_SPACE 1024 +#define MIN_DISK_SPACE 10485760 /* 10 MB */ + +/***** Data Definitions *****/ + +/* possible changelog open modes */ +typedef enum +{ + CL5_OPEN_NONE, /* nothing specified */ + CL5_OPEN_NORMAL, /* open for normal read/write use */ + CL5_OPEN_RESTORE_RECOVER, /* restore from archive and recover */ + CL5_OPEN_RESTORE, /* restore, but no recovery */ + CL5_OPEN_LDIF2CL, /* open as part of ldif2cl: no locking, + recovery, checkpointing */ + CL5_OPEN_CLEAN_RECOVER /* remove env after recover open (upgrade) */ +} CL5OpenMode; + +#define DB_FILE_DELETED 0x1 +#define DB_FILE_INIT 0x2 +/* this structure represents one changelog file, Each changelog file contains + changes applied to a single backend. Files are named by the database id */ +typedef struct cl5dbfile +{ + char *name; /* file name (with the extension) */ + char *replGen; /* replica generation of the data */ + char *replName; /* replica name */ + DB *db; /* db handle to the changelog file*/ + int entryCount; /* number of entries in the file */ + int flags; /* currently used to mark the file as deleted + * or as initialized */ + RUV *purgeRUV; /* ruv to which the file has been purged */ + RUV *maxRUV; /* ruv that marks the upper boundary of the data */ + char *semaName; /* semaphore name */ + PRSem *sema; /* semaphore for max concurrent cl writes */ +}CL5DBFile; + +/* structure that allows to iterate through entries to be sent to a consumer + that originated on a particular supplier. */ +struct cl5replayiterator +{ + Object *fileObj; + CLC_Buffer *clcache; /* changelog cache */ + ReplicaId consumerRID; /* consumer's RID */ + const RUV *consumerRuv; /* consumer's update vector */ + Object *supplierRuvObj;/* supplier's update vector object */ +}; + +typedef struct cl5iterator +{ + DBC *cursor; /* current position in the db file */ + Object *file; /* handle to release db file object */ +}CL5Iterator; + +/* changelog trimming configuration */ +typedef struct cl5trim +{ + time_t maxAge; /* maximum entry age in seconds */ + int maxEntries; /* maximum number of entries across all changelog files */ + PRLock* lock; /* controls access to trimming configuration */ +} CL5Trim; + +/* this structure defines 5.0 changelog internals */ +typedef struct cl5desc +{ + char *dbDir; /* absolute path to changelog directory */ + DB_ENV *dbEnv; /* db environment shared by all db files */ + int dbEnvOpenFlags;/* openflag used for env->open */ + Objset *dbFiles; /* ref counted set of changelog files (CL5DBFile) */ + PRLock *fileLock; /* ensures that changelog file is not added twice */ + CL5OpenMode dbOpenMode; /* how we open db */ + CL5DBConfig dbConfig; /* database configuration params */ + CL5Trim dbTrim; /* trimming parameters */ + CL5State dbState; /* changelog current state */ + PRRWLock *stLock; /* lock that controls access to the changelog state */ + PRBool dbRmOnClose;/* indicates whether changelog should be removed when + it is closed */ + PRBool fatalError; /* bad stuff happened like out of disk space; don't + write guardian file on close - UnUsed so far */ + int threadCount;/* threads that globally access changelog like + deadlock detection, etc. */ + PRLock *clLock; /* Lock associated to clVar, used to notify threads on close */ + PRCondVar *clCvar; /* Condition Variable used to notify threads on close */ +} CL5Desc; + +typedef void (*VFP)(void *); + +int g_get_shutdown(); /* declared in proto-slap.h */ + +/***** Global Variables *****/ +static CL5Desc s_cl5Desc; + +/***** Forward Declarations *****/ + +/* changelog initialization and cleanup */ +static int _cl5Open (const char *dir, const CL5DBConfig *config, CL5OpenMode openMode); +static int _cl5AppInit (PRBool *didRecovery); +static int _cl5DBOpen (); +static void _cl5SetDefaultDBConfig (); +static void _cl5SetDBConfig (const CL5DBConfig *config); +static void _cl5InitDBEnv(DB_ENV *dbEnv); +static int _cl5CheckDBVersion (); +static int _cl5ReadDBVersion (const char *dir, char *clVersion); +static int _cl5WriteDBVersion (); +static int _cl5CheckGuardian (); +static int _cl5ReadGuardian (char *buff); +static int _cl5WriteGuardian (); +static int _cl5RemoveGuardian (); +static void _cl5Close (); +static int _cl5Delete (const char *dir, PRBool rmDir); +static void _cl5DBClose (); + +/* thread management */ +static int _cl5DispatchDBThreads (); +static int _cl5AddThread (); +static void _cl5RemoveThread (); +static int _cl5DeadlockMain (void *param); +static int _cl5CheckpointMain (void *param); +static int _cl5TrickleMain (void *param); + +/* functions that work with individual changelog files */ +static int _cl5NewDBFile (const char *replName, const char *replGen, CL5DBFile** dbFile); +static int _cl5DBOpenFile (Object *replica, Object **obj, PRBool checkDups); +static int _cl5DBOpenFileByReplicaName (const char *replName, const char *replGen, + Object **obj, PRBool checkDups); +static void _cl5DBCloseFile (void **data); +static void _cl5DBDeleteFile (Object *obj); +static void _cl5DBFileInitialized (Object *obj); +static int _cl5GetDBFile (Object *replica, Object **obj); +static int _cl5GetDBFileByReplicaName (const char *replName, const char *replGen, + Object **obj); +static int _cl5AddDBFile (CL5DBFile *file, Object **obj); +static int _cl5CompareDBFile (Object *el1, const void *el2); +static int _cl5CopyDBFiles (const char *srcDir, const char *distDir, Object **replicas); +static char* _cl5Replica2FileName (Object *replica); +static char* _cl5MakeFileName (const char *replName, const char *replGen); +static PRBool _cl5FileName2Replica (const char *fileName, Object **replica); +static int _cl5ExportFile (PRFileDesc *prFile, Object *obj); +static PRBool _cl5ReplicaInList (Object *replica, Object **replicas); + +/* data storage and retrieval */ +static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len); +static int _cl5WriteOperation(const char *replName, const char *replGen, + const slapi_operation_parameters *op, PRBool local); +static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid); +static int _cl5GetNextEntry (CL5Entry *entry, void *iterator); +static int _cl5CurrentDeleteEntry (void *iterator); +static PRBool _cl5IsValidIterator (const CL5Iterator *iterator); +static int _cl5GetOperation (Object *replica, slapi_operation_parameters *op); +static const char* _cl5OperationType2Str (int type); +static int _cl5Str2OperationType (const char *str); +static void _cl5WriteString (const char *str, char **buff); +static void _cl5ReadString (char **str, char **buff); +static void _cl5WriteMods (LDAPMod **mods, char **buff); +static void _cl5WriteMod (LDAPMod *mod, char **buff); +static int _cl5ReadMods (LDAPMod ***mods, char **buff); +static int _cl5ReadMod (Slapi_Mod *mod, char **buff); +static int _cl5GetModsSize (LDAPMod **mods); +static int _cl5GetModSize (LDAPMod *mod); +static void _cl5ReadBerval (struct berval *bv, char** buff); +static void _cl5WriteBerval (struct berval *bv, char** buff); +static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size); +static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size); + +/* replay iteration */ +static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator); +static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv, + Object *replica, Object *fileObject, CL5ReplayIterator **iterator); +static int _cl5CheckMissingCSN (const CSN *minCsn, const RUV *supplierRUV, CL5DBFile *file); + +/* changelog trimming */ +static int _cl5TrimInit (); +static void _cl5TrimCleanup (); +static int _cl5TrimMain (void *param); +static void _cl5DoTrimming (); +static void _cl5TrimFile (Object *obj, long *numToTrim); +static PRBool _cl5CanTrim (time_t time, long *numToTrim); +static int _cl5ReadRUV (const char *replGen, Object *obj, PRBool purge); +static int _cl5WriteRUV (CL5DBFile *file, PRBool purge); +static int _cl5ConstructRUV (const char *replGen, Object *obj, PRBool purge); +static int _cl5UpdateRUV (Object *obj, CSN *csn, PRBool newReplica, PRBool purge); +static int _cl5GetRUV2Purge2 (Object *fileObj, RUV **ruv); + +/* db error processing */ +static void _cl5DBLogPrint(const char* prefix, char *buffer); + +/* bakup/recovery, import/export */ +static PRBool _cl5IsLogFile (const char *name); +static int _cl5Recover (int open_flags, DB_ENV *dbEnv); +static int _cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, + char **replGen); +static int _cl5Operation2LDIF (const slapi_operation_parameters *op, const char *replGen, + char **ldifEntry, PRInt32 *lenLDIF); + +/* entry count */ +static int _cl5GetEntryCount (CL5DBFile *file); +static int _cl5WriteEntryCount (CL5DBFile *file); + +/* misc */ +static char* _cl5GetHelperEntryKey (int type, char *csnStr); +static Object* _cl5GetReplica (const slapi_operation_parameters *op, const char* replGen); +static int _cl5FileEndsWith(const char *filename, const char *ext); + +/* Callback function for libdb to spit error info into our log */ +static void dblayer_log_print(const char* prefix, char *buffer) +{ + /* We ignore the prefix since we know who we are anyway */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "libdb: %s\n", buffer); +} + +static PRLock *cl5_diskfull_lock = NULL; +static int cl5_diskfull_flag = 0; + +static void cl5_set_diskfull(); +static void cl5_set_no_diskfull(); + +/***** Module APIs *****/ + +/* Name: cl5Init + Description: initializes changelog module; must be called by a single thread + before any other changelog function. + Parameters: none + Return: CL5_SUCCESS if function is successful; + CL5_SYSTEM_ERROR error if NSPR call fails. + */ +int cl5Init () +{ + s_cl5Desc.stLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "state_lock"); + if (s_cl5Desc.stLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Init: failed to create state lock; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + if ((s_cl5Desc.clLock = PR_NewLock()) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Init: failed to create on close lock; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + + } + if ((s_cl5Desc.clCvar = PR_NewCondVar(s_cl5Desc.clLock)) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Init: failed to create on close cvar; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (( clcache_init (&s_cl5Desc.dbEnv) != 0 )) { + return CL5_SYSTEM_ERROR; + } + + s_cl5Desc.dbState = CL5_STATE_CLOSED; + s_cl5Desc.fatalError = PR_FALSE; + s_cl5Desc.dbRmOnClose = PR_FALSE; + s_cl5Desc.threadCount = 0; + + if (NULL == cl5_diskfull_lock) + { + cl5_diskfull_lock = PR_NewLock (); + } + + return CL5_SUCCESS; +} + +/* Name: cl5Cleanup + Description: performs cleanup of the changelog module; must be called by a single + thread; it closes changelog if it is still open. + Parameters: none + Return: none + */ +void cl5Cleanup () +{ + /* close db if it is still open */ + if (s_cl5Desc.dbState == CL5_STATE_OPEN) + { + cl5Close (); + } + + if (s_cl5Desc.stLock) + PR_DestroyRWLock (s_cl5Desc.stLock); + s_cl5Desc.stLock = NULL; + + if (cl5_diskfull_lock) + { + PR_DestroyLock (cl5_diskfull_lock); + cl5_diskfull_lock = NULL; + } + + memset (&s_cl5Desc, 0, sizeof (s_cl5Desc)); +} + +/* Name: cl5Open + Description: opens changelog; must be called after changelog is + initialized using cl5Init. It is thread safe and the second + call is ignored. + Parameters: dir - changelog dir + config - db configuration parameters; currently not used + Return: CL5_SUCCESS if successfull; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_STATE if changelog is not initialized; + CL5_BAD_DBVERSION if dbversion file is missing or has unexpected data + CL5_SYSTEM_ERROR if NSPR error occured (during db directory creation); + CL5_MEMORY_ERROR if memory allocation fails; + CL5_DB_ERROR if db initialization fails. + */ +int cl5Open (const char *dir, const CL5DBConfig *config) +{ + int rc; + + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Open: null directory\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* prevent state from changing */ + PR_RWLock_Wlock (s_cl5Desc.stLock); + + /* already open - ignore */ + if (s_cl5Desc.dbState == CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Open: changelog already opened; request ignored\n"); + rc = CL5_SUCCESS; + goto done; + } + else if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: invalid state - %d\n", s_cl5Desc.dbState); + rc = CL5_BAD_STATE; + goto done; + } + + rc = _cl5Open (dir, config, CL5_OPEN_NORMAL); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: failed to open changelog\n"); + goto done; + } + + /* dispatch global threads like deadlock detection, trimming, etc */ + rc = _cl5DispatchDBThreads (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Open: failed to start database monitoring threads\n"); + + _cl5Close (); + } + else + { + s_cl5Desc.dbState = CL5_STATE_OPEN; + } + +done:; + PR_RWLock_Unlock (s_cl5Desc.stLock); + + return rc; +} + +/* Name: cl5Close + Description: closes changelog; waits until all threads are done using changelog; + call is ignored if changelog is already closed. + Parameters: none + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if db is not in the open or closed state; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_DB_ERROR if db shutdown fails + */ +int cl5Close () +{ + int rc = CL5_SUCCESS; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Close: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + PR_RWLock_Wlock (s_cl5Desc.stLock); + + /* already closed - ignore */ + if (s_cl5Desc.dbState == CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Close: changelog closed; request ignored\n"); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_SUCCESS; + } + else if (s_cl5Desc.dbState != CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Close: invalid state - %d\n", s_cl5Desc.dbState); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + /* signal changelog closing to all threads */ + s_cl5Desc.dbState = CL5_STATE_CLOSING; + + PR_Lock(s_cl5Desc.clLock); + PR_NotifyCondVar(s_cl5Desc.clCvar); + PR_Unlock(s_cl5Desc.clLock); + + _cl5Close (); + + s_cl5Desc.dbState = CL5_STATE_CLOSED; + + PR_RWLock_Unlock (s_cl5Desc.stLock); + + return rc; +} + +/* Name: cl5Delete + Description: removes changelog; changelog must be in the closed state. + Parameters: dir - changelog directory + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not in closed state; + CL5_BAD_DATA if invalid directory supplied + CL5_SYSTEM_ERROR if NSPR call fails + */ +int cl5Delete (const char *dir) +{ + int rc; + + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, "cl5Delete: null directory\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Delete: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + PR_RWLock_Wlock (s_cl5Desc.stLock); + + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Delete: invalid state - %d\n", s_cl5Desc.dbState); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + rc = _cl5Delete (dir, PR_TRUE /* remove changelog dir */); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Delete: failed to remove changelog\n"); + } + + PR_RWLock_Unlock (s_cl5Desc.stLock); + return rc; +} + +/* Name: cl5OpenDB + Description: opens changelog file for specified file + Parameters: replica - replica whose file we wish to open + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + */ +int cl5OpenDB (Object *replica) +{ + int rc; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5OpenDB: null replica\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5OpenDB: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5DBOpenFile (replica, NULL /* file object */, PR_TRUE /* check for duplicates */); + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5CloseDB + Description: closes changelog file for the specified replica + Parameters: replica - replica whose file we wish to close + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5CloseDB (Object *replica) +{ + int rc; + Object *obj; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5CloseDB: null replica\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CloseDB: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + rc = objset_remove_obj(s_cl5Desc.dbFiles, obj); + object_release (obj); + } + else + { + Replica *r; + + r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CloseDB: failed to close file for replica at (%s)\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5DeleteDB + Description: asynchronously removes changelog file for the specified replica. + The file is physically removed when it is no longer in use. + This function is called when a backend is removed or reloaded. + Parameters: replica - replica whose file we wish to delete + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5DeleteDB (Object *replica) +{ + Object *obj; + int rc; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5DeleteDB: invalid database id\n"); + return CL5_BAD_DATA; + } + + /* changelog is not initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDB: " + "changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + _cl5DBDeleteFile (obj); + } + else + { + Replica *r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDB: " + "file for replica at (%s) not found\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5DeleteDBSync + Description: The same as cl5DeleteDB except the function does not return + until the file is removed. +*/ +int cl5DeleteDBSync (Object *replica) +{ + Object *obj; + int rc; + CL5DBFile *file; + char fName [MAXPATHLEN + 1]; + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5DeleteDBSync: invalid database id\n"); + return CL5_BAD_DATA; + } + + /* changelog is not initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDBSync: " + "changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, file->name); + + _cl5DBDeleteFile (obj); + + /* wait until the file is gone */ + while (PR_Access (fName, PR_ACCESS_EXISTS) == PR_SUCCESS) + { + DS_Sleep (PR_MillisecondsToInterval(100)); + } + + } + else + { + Replica *r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5DeleteDBSync: " + "file for replica at (%s) not found\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5GetUpperBoundRUV + Description: retrieves vector for that represnts the upper bound of the changes for a replica. + Parameters: r - replica for which the purge vector is requested + ruv - contains a copy of the purge ruv if function is successful; + unchanged otherwise. It is responsobility pf the caller to free + the ruv when it is no longer is in use + Return: CL5_SUCCESS if function is successfull + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND, if changelog file for replica is not found + */ +int cl5GetUpperBoundRUV (Replica *r, RUV **ruv) +{ + int rc; + Object *r_obj, *file_obj; + CL5DBFile *file; + + if (r == NULL || ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetUpperBoundRUV: invalid parameters\n"); + return CL5_BAD_DATA; + } + + /* changelog is not initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetUpperBoundRUV: " + "changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + /* create a temporary replica object because of the interface we have */ + r_obj = object_new (r, NULL); + + rc = _cl5GetDBFile (r_obj, &file_obj); + if (rc == CL5_SUCCESS) + { + file = (CL5DBFile*)object_get_data (file_obj); + PR_ASSERT (file && file->maxRUV); + + *ruv = ruv_dup (file->maxRUV); + + object_release (file_obj); + } + + object_release (r_obj); + + _cl5RemoveThread (); + return rc; +} + +/* Name: cl5Backup + Description: makes a backup of the changelog including *.db2, + log files, and dbversion. Can be called with the changelog in either open or + closed state. + Parameters: bkDir - directory to which the data is backed up; + created if it does not exist + replicas - optional list of replicas whose changes should be backed up; + if the list is NULL, entire changelog is backed up. + Return: CL5_SUCCESS if function is successful; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_STATE if changelog has not been initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call or file copy failes. + */ +int cl5Backup (const char *bkDir, Object **replicas) +{ + int rc; + char **list = NULL; + char **logFile; + char srcFile [MAXPATHLEN + 1]; + char destFile[MAXPATHLEN + 1]; + DB_TXN *txn = NULL; + + if (bkDir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Backup: null backup directory\n"); + return CL5_BAD_DATA; + } + + /* changelog must be initialized */ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + /* create backup directory if necessary */ + rc = cl5CreateDirIfNeeded (bkDir); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to create backup directory\n"); + goto done; + } + + /* start transaction to tempararily prevent transaction log + from being trimmed + */ + rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL /*pid*/, &txn, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Backup: starting changelog backup from %s to %s ...\n", s_cl5Desc.dbDir, bkDir); + + /* The following files are backed up: *.<dbext>, log files, dbversion file */ + + /* copy db file */ + /* ONREPL currently, list of replicas is ignored because db code can't handle + discrepancy between transaction log and present files; should be fixed before 5.0 ships */ + rc = _cl5CopyDBFiles (s_cl5Desc.dbDir, bkDir, replicas); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup : failed to copy database files from %s to %s\n", s_cl5Desc.dbDir, bkDir); + goto done; + } + + /* copy db log files */ + rc = LOG_ARCHIVE(s_cl5Desc.dbEnv, &list, DB_ARCH_LOG, malloc); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to get list of log files; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + if (list) + { + logFile = list; + while (*logFile) + { + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, *logFile); + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", bkDir, *logFile); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to copy %s\n", *logFile); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + logFile ++; + } + + free(list); + } + + /* now, copy the version file */ + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, VERSION_FILE); + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", bkDir, VERSION_FILE); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to copy %s\n", VERSION_FILE); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + rc = CL5_SUCCESS; + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Backup: changelog backup is finished \n"); +done:; + if (txn && TXN_ABORT (txn) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Backup: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + } + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5Restore + Description: restores changelog from the backed up copy. Changelog must be ibnitalized and closed. + Parameters: clDir - changelog dir + bkDir - directory that contains the backup + replicas - optional list of replicas whose changes should be recovered; + if the list is NULL, entire changelog is recovered. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call of file copy fails + */ +int cl5Restore (const char *clDir, const char *bkDir, Object **replicas) +{ + int rc; + char srcFile[MAXPATHLEN + 1]; + char destFile[MAXPATHLEN + 1]; + PRDir *prDir; + PRDirEntry *prDirEntry; + int seenLog = 0; /* Tells us if we restored any logfiles */ + + if (clDir == NULL || bkDir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5Restore: null parameter\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* prevent state change while recovery is in progress */ + PR_RWLock_Wlock (s_cl5Desc.stLock); + + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: changelog must be closed\n"); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Restore: starting changelog recovery from %s to %s ...\n", bkDir, clDir); + + /* delete current changelog content */ + rc = _cl5Delete (clDir, PR_FALSE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: failed to remove changelog\n"); + goto done; + } + + /* We copy the files over from the staging area */ + prDir = PR_OpenDir(bkDir); + if (prDir == NULL) + { + rc = CL5_SYSTEM_ERROR; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: unable to access backup directory %s; NSPR error - %d\n", + bkDir, PR_GetError ()); + goto done; + } + + while (NULL != (prDirEntry = PR_ReadDir(prDir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == prDirEntry->name) /* NSPR doesn't behave like the docs say it should */ + { + break; + } + + /* Log files have names of the form "log.xxxxx". We detect these by looking for + the prefix "log." and the lack of the ".<dbext>" suffix */ + seenLog |= _cl5IsLogFile(prDirEntry->name); + + /* ONREPL currently, list of replicas is ignored because db code can't handle discrepancy + between transaction log and present files; this should change before 5.0 ships */ + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", clDir, prDirEntry->name); + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", bkDir, prDirEntry->name); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + rc = CL5_SYSTEM_ERROR; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: failed to copy %s\n", prDirEntry->name); + PR_CloseDir(prDir); + goto done; + } + } + + PR_CloseDir(prDir); + + /* now open and close changelog to create all necessary files */ + if (seenLog) + rc = _cl5Open (clDir, NULL, CL5_OPEN_RESTORE_RECOVER); + else + rc = _cl5Open (clDir, NULL, CL5_OPEN_RESTORE); + + if (rc == CL5_SUCCESS) + { + _cl5Close (); + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5Restore: changelog recovery is finished \n"); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5Restore: failed open changelog after recovery\n"); + } + +done:; + PR_RWLock_Unlock (s_cl5Desc.stLock); + return rc; +} + +/* Name: cl5ExportLDIF + Description: dumps changelog to an LDIF file; changelog can be open or closed. + Parameters: clDir - changelog dir + ldifFile - full path to ldif file to write + replicas - optional list of replicas whose changes should be exported; + if the list is NULL, entire changelog is exported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is not initialized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ExportLDIF (const char *ldifFile, Object **replicas) +{ + int i; + int rc; + PRFileDesc *prFile = NULL; + Object *obj; + + if (ldifFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ExportLDIF: null ldif file name\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ExportLDIF: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + prFile = PR_Open (ldifFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600); + if (prFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ExportLDIF: failed to open (%s) file; NSPR error - %d\n", + ldifFile, PR_GetError ()); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5ExportLDIF: starting changelog export to (%s) ...\n", ldifFile); + + if (replicas) /* export only selected files */ + { + for (i = 0; replicas[i]; i++) + { + rc = _cl5GetDBFile (replicas[i], &obj); + if (rc == CL5_SUCCESS) + { + rc = _cl5ExportFile (prFile, obj); + object_release (obj); + } + else + { + Replica *r = (Replica*)object_get_data (replicas[i]); + + PR_ASSERT (r); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5ExportLDIF: " + "failed to locate changelog file for replica at (%s)\n", + slapi_sdn_get_dn (replica_get_root (r))); + } + } + } + else /* export all files */ + { + for (obj = objset_first_obj(s_cl5Desc.dbFiles); obj; + obj = objset_next_obj(s_cl5Desc.dbFiles, obj)) + { + rc = _cl5ExportFile (prFile, obj); + object_release (obj); + } + } + + rc = CL5_SUCCESS; +done:; + + _cl5RemoveThread (); + + if (rc == CL5_SUCCESS) + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5ExportLDIF: changelog export is finished.\n"); + + if (prFile) + PR_Close (prFile); + + return rc; +} + +/* Name: cl5ImportLDIF + Description: imports ldif file into changelog; changelog must be in the closed state + Parameters: clDir - changelog dir + ldifFile - absolute path to the ldif file to import + replicas - optional list of replicas whose data should be imported; + if the list is NULL, all data in the file is imported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not inititalized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ImportLDIF (const char *clDir, const char *ldifFile, Object **replicas) +{ + FILE *file; + int rc; + char *buff; + int lineno = 0; + slapi_operation_parameters op; + Object *replica = NULL; + char *replGen = NULL; + + /* validate params */ + if (ldifFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: null ldif file name\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that nobody change changelog state while import is in progress */ + PR_RWLock_Wlock (s_cl5Desc.stLock); + + /* make sure changelog is closed */ + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: invalid state - %d \n", s_cl5Desc.dbState); + + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + /* open LDIF file */ + file = fopen (ldifFile, "r"); /* XXXggood Does fopen reliably work if > 255 files open? */ + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to open (%s) ldif file; system error - %d\n", + ldifFile, errno); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + /* remove changelog */ + rc = _cl5Delete (clDir, PR_FALSE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to remove changelog\n"); + goto done; + } + + /* open changelog */ + rc = _cl5Open (clDir, NULL, CL5_OPEN_LDIF2CL); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to open changelog\n"); + goto done; + } + + /* read entries and write them to changelog */ + while ((buff = ldif_get_entry( file, &lineno )) != NULL) + { + rc = _cl5LDIF2Operation (buff, &op, &replGen); + slapi_ch_free ((void**)&buff); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to convert LDIF fragment to LDAP operation; " + "end of fragment line number - %d\n", lineno); + goto done; + } + + /* if we perform selective import, check if the operation should be wriiten to changelog */ + replica = _cl5GetReplica (&op, replGen); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to locate replica for target dn (%s) and " + "replica generation %s\n", op.target_address.dn, replGen); + + slapi_ch_free ((void**)&replGen); + operation_parameters_done (&op); + goto done; + } + + if (!replicas || _cl5ReplicaInList (replica, replicas)) + { + /* write operation creates the file if it does not exist */ + rc = _cl5WriteOperation (replica_get_name ((Replica*)object_get_data(replica)), + replGen, &op, 1); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ImportLDIF: failed to write operation to the changelog\n"); + object_release (replica); + slapi_ch_free ((void**)&replGen); + operation_parameters_done (&op); + goto done; + } + } + + object_release (replica); + slapi_ch_free ((void**)&replGen); + operation_parameters_done (&op); + } + +done:; + _cl5Close (); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return rc; +} + +/* Name: cl5GetState + Description: returns database state + Parameters: none + Return: changelog state + */ +int cl5GetState () +{ + return s_cl5Desc.dbState; +} + +/* Name: cl5ConfigTrimming + Description: sets changelog trimming parameters; changelog must be open. + Parameters: maxEntries - maximum number of entries in the chnagelog (in all files); + maxAge - maximum entry age; + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if changelog is not open + */ +int cl5ConfigTrimming (int maxEntries, const char *maxAge) +{ + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5ConfigTrimming: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure changelog is not closed while trimming configuration + is updated.*/ + _cl5AddThread (); + + PR_Lock (s_cl5Desc.dbTrim.lock); + + if (maxAge) + { + /* don't ignore this argument */ + if (strcmp (maxAge, CL5_STR_IGNORE) != 0) + { + s_cl5Desc.dbTrim.maxAge = age_str2time (maxAge); + } + } + else + { + /* unlimited */ + s_cl5Desc.dbTrim.maxAge = 0; + } + + if (maxEntries != CL5_NUM_IGNORE) + { + s_cl5Desc.dbTrim.maxEntries = maxEntries; + } + + PR_Unlock (s_cl5Desc.dbTrim.lock); + + _cl5RemoveThread (); + + return CL5_SUCCESS; +} + +/* Name: cl5GetOperation + Description: retireves operation specified by its csn and databaseid + Parameters: op - must contain csn and databaseid; the rest of data is + filled if function is successfull + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_NOTFOUND if entry was not found; + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if db data format does not match entry format. + */ +int cl5GetOperation (Object *replica, slapi_operation_parameters *op) +{ + int rc; + char *agmt_name; + + agmt_name = get_thread_private_agmtname(); + + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetOperation: NULL replica\n"); + return CL5_BAD_DATA; + } + + if (op == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "cl5GetOperation: NULL operation\n"); + return CL5_BAD_DATA; + } + + if (op->csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "%s: cl5GetOperation: operation contains no CSN\n", agmt_name); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: cl5GetOperation: changelog is not initialized\n", agmt_name); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetOperation (replica, op); + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5GetFirstOperation + Description: retrieves first operation for a particular database + replica - replica for which the operation should be retrieved. + Parameters: op - buffer to store the operation; + iterator - to be passed to the call to cl5GetNextOperation + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if operation is NULL + CL5_BAD_STATE, if changelog is not open + CL5_DB_ERROR, if db call fails + */ +int cl5GetFirstOperation (Object *replica, slapi_operation_parameters *op, void **iterator) +{ + int rc; + CL5Entry entry; + Object *obj; + char *agmt_name; + + if (replica == NULL || op == NULL || iterator == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetFirstOperation: invalid argument\n"); + return CL5_BAD_DATA; + } + + *iterator = NULL; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + agmt_name = get_thread_private_agmtname(); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: cl5GetFirstOperation: changelog is not initialized\n", agmt_name); + return CL5_BAD_STATE; + } + + /* make sure that changelog stays open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5GetDBFile (replica, &obj); + if (rc != CL5_SUCCESS) + { + _cl5RemoveThread (); + return rc; + } + + entry.op = op; + /* Callers of this function should cl5_operation_parameters_done(op) */ + rc = _cl5GetFirstEntry (obj, &entry, iterator, NULL); + object_release (obj); + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5GetNextOperation + Description: retrieves the next op from the changelog as defined by the iterator; + changelog must be open. + Parameters: op - returned operation, if function is successful + iterator - in: identifies op to retrieve; out: identifies next op + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if op is NULL + CL5_BAD_STATE, if changelog is not open + CL5_NOTFOUND, empty changelog + CL5_DB_ERROR, if db call fails + */ +int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator) +{ + CL5Entry entry; + + if (op == NULL || iterator == NULL || !_cl5IsValidIterator (iterator)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetNextOperation: invalid argument\n"); + return CL5_BAD_DATA; + } + + if (s_cl5Desc.dbState != CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetNextOperation: changelog is not open\n"); + return CL5_BAD_STATE; + } + + /* we don't need to increment thread count since cl5GetFirstOperation + locked the file through which we are iterating */ + entry.op = op; + /* Callers of this function should cl5_operation_parameters_done(op) */ + return _cl5GetNextEntry (&entry, iterator); +} + +/* Name: cl5DestroyIterator + Description: destroys iterator once iteration through changelog is done + Parameters: iterator - iterator to destroy + Return: none + */ +void cl5DestroyIterator (void *iterator) +{ + CL5Iterator *it = (CL5Iterator*)iterator; + + if (it == NULL) + return; + + /* close cursor */ + if (it->cursor) + it->cursor->c_close (it->cursor); + + if (it->file) + object_release (it->file); + + slapi_ch_free ((void**)&it); +} + +/* Name: cl5WriteOperation + Description: writes operation to changelog + Parameters: replName - name of the replica to which operation applies + replGen - replica generation for the operation + !!!Note that we pass name and generation rather than + replica object since generation can change while operation + is in progress (if the data is reloaded). !!! + op - operation to write + local - this is a non-replicated operation + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_MEMORY_ERROR if memory allocation failed; + CL5_DB_ERROR if any other db error occured; + */ +int cl5WriteOperation(const char *replName, const char *replGen, + const slapi_operation_parameters *op, PRBool local) +{ + int rc; + + if (op == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5WriteOperation: NULL operation passed\n"); + return CL5_BAD_DATA; + } + + if (!IsValidOperation (op)) + { + return CL5_BAD_DATA; + } + + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5WriteOperation: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return rc; + + rc = _cl5WriteOperation(replName, replGen, op, local); + + /* update the upper bound ruv vector */ + if (rc == CL5_SUCCESS) + { + Object *file_obj = NULL; + + if ( _cl5GetDBFileByReplicaName (replName, replGen, &file_obj) == CL5_SUCCESS) { + rc = _cl5UpdateRUV (file_obj, op->csn, PR_FALSE, PR_FALSE); + object_release (file_obj); + } + + } + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5CreateReplayIterator + Description: creates an iterator that allows to retireve changes that should + to be sent to the consumer identified by ruv. The iteration is peformed by + repeated calls to cl5GetNextOperationToReplay. + Parameters: replica - replica whose data we wish to iterate; + ruv - consumer ruv; + iterator - iterator to be passed to cl5GetNextOperationToReplay call + Return: CL5_SUCCESS, if function is successfull; + CL5_MISSING_DATA, if data that should be in the changelog is missing + CL5_PURGED_DATA, if some data that consumer needs has been purged. + Note that the iterator can be non null if the supplier contains + some data that needs to be sent to the consumer + CL5_NOTFOUND if the consumer is up to data with respect to the supplier + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if db has not been open; + CL5_DB_ERROR if any other db error occured; + CL5_MEMORY_ERROR if memory allocation fails. + Algorithm: Build a list of csns from consumer's and supplier's ruv. For each element + of the consumer's ruv put max csn into the csn list. For each element + of the supplier's ruv not in the consumer's ruv put min csn from the + supplier's ruv into the list. The list contains, for each known replica, + the starting point for changes to be sent to the consumer. + Sort the list in accending order. + Build a hash which contains, for each known replica, whether the + supplier can bring the consumer up to data with respect to that replica. + The hash is used to decide whether a change can be sent to the consumer + Find the replica with the smallest csn in the list for which + we can bring the consumer up to date. + Position the db cursor on the change entry that corresponds to this csn. + Hash entries are created for each replica traversed so far. sendChanges + flag is set to FALSE for all repolicas except the last traversed. + + */ +int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *consumerRuv, + CL5ReplayIterator **iterator) +{ + int rc; + Object *replica; + Object *obj = NULL; + + replica = prp->replica_object; + if (replica == NULL || consumerRuv == NULL || iterator == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateReplayIterator: invalid parameter\n"); + return CL5_BAD_DATA; + } + + *iterator = NULL; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateReplayIterator: changelog is not initialized\n"); + return CL5_BAD_STATE; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS ) return rc; + + + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + /* iterate through the ruv in csn order to find first master for which + we can replay changes */ + ReplicaId consumerRID = agmt_get_consumer_rid ( prp->agmt, prp->conn ); + rc = _cl5PositionCursorForReplay (consumerRID, consumerRuv, replica, obj, iterator); + if (rc != CL5_SUCCESS) + { + if (obj) + object_release (obj); + } + } + + _cl5RemoveThread (); + + return rc; +} + +/* Name: cl5GetNextOperationToReplay + Description: retrieves next operation to be sent to a particular consumer and + that was created on a particular master. Consumer and master info + is encoded in the iterator parameter that must be created by call + to cl5CreateReplayIterator. + Parameters: iterator - iterator that identifies next entry to retrieve; + op - operation retrieved if function is successful + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_NOTFOUND if end of iteration list is reached + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if data in db is of unrecognized format; + CL5_MEMORY_ERROR if memory allocation fails. + Algorithm: Iterate through changelog entries until a change is found that + originated at the replica for which we are sending changes + (based on the information in the iteration hash) and + whose csn is larger than the csn already seen by the consumer + If change originated at the replica not in the hash, + determine whether we should send changes originated at the replica + and add replica entry into the hash. We can send the changes for + the replica if the current csn is smaller or equal to the csn + in the consumer's ruv (if present) or if it is equal to the min + csn in the supplier's ruv. + */ +int +cl5GetNextOperationToReplay (CL5ReplayIterator *iterator, CL5Entry *entry) +{ + CSN *csn; + char *key, *data; + size_t keylen, datalen; + char *agmt_name; + int rc = 0; + + agmt_name = get_thread_private_agmtname(); + + if (entry == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: cl5GetNextOperationToReplay: invalid parameter passed\n", agmt_name); + return CL5_BAD_DATA; + } + + rc = clcache_get_next_change (iterator->clcache, (void **)&key, &keylen, (void **)&data, &datalen, &csn); + + if (rc == DB_NOTFOUND) + { + /* + * Abort means we've figured out that we've passed the replica Min CSN, + * so we should stop looping through the changelog + */ + return CL5_NOTFOUND; + } + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, NULL, "%s: cl5GetNextOperationToReplay: " + "failed to read next entry; DB error %d\n", agmt_name, rc); + return CL5_DB_ERROR; + } + + /* there is an entry we should return */ + /* Callers of this function should cl5_operation_parameters_done(op) */ + if ( 0 != cl5DBData2Entry ( data, datalen, entry ) ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: cl5GetNextOperationToReplay: failed to format entry rc=%d\n", agmt_name, rc); + return rc; + } + + return CL5_SUCCESS; +} + +/* Name: cl5DestroyReplayIterator + Description: destorys iterator + Parameters: iterator - iterator to destory + Return: none + */ +void cl5DestroyReplayIterator (CL5ReplayIterator **iterator) +{ + if (iterator == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5DestroyReplayIterator: invalid iterartor passed\n"); + return; + } + + clcache_return_buffer ( &(*iterator)->clcache ); + + if ((*iterator)->fileObj) + object_release ((*iterator)->fileObj); + + /* release supplier's ruv */ + if ((*iterator)->supplierRuvObj) + object_release ((*iterator)->supplierRuvObj); + + slapi_ch_free ((void **)iterator); +} + +/* Name: cl5DeleteOnClose + Description: marks changelog for deletion when it is closed + Parameters: flag; if flag = 1 then delete else don't + Return: none + */ +void cl5DeleteOnClose (PRBool rm) +{ + s_cl5Desc.dbRmOnClose = rm; +} + +/* Name: cl5GetDir + Description: returns changelog directory + Parameters: none + Return: copy of the directory; caller needs to free the string + */ + char *cl5GetDir () +{ + if (s_cl5Desc.dbDir == NULL) + { + return NULL; + } + else + { + return slapi_ch_strdup (s_cl5Desc.dbDir); + } +} + +/* Name: cl5Exist + Description: checks if a changelog exists in the specified directory; + We consider changelog to exist if it contains the dbversion file. + Parameters: clDir - directory to check + Return: 1 - if changelog exists; 0 - otherwise + */ +PRBool cl5Exist (const char *clDir) +{ + char fName [MAXPATHLEN + 1]; + int rc; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", clDir, VERSION_FILE); + rc = PR_Access (fName, PR_ACCESS_EXISTS); + + return (rc == PR_SUCCESS); +} + +/* Name: cl5GetOperationCount + Description: returns number of entries in the changelog. The changelog must be + open for the value to be meaningful. + Parameters: replica - optional parameter that specifies the replica whose operations + we wish to count; if NULL all changelog entries are counted + Return: number of entries in the changelog + */ + +int cl5GetOperationCount (Object *replica) +{ + Object *obj; + CL5DBFile *file; + int count = 0; + int rc; + + if (s_cl5Desc.dbState == CL5_STATE_NONE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5GetOperationCount: changelog is not initialized\n"); + return -1; + } + + /* make sure that changelog is open while operation is in progress */ + rc = _cl5AddThread (); + if (rc != CL5_SUCCESS) + return -1; + + if (replica == NULL) /* compute total entry count */ + { + obj = objset_first_obj (s_cl5Desc.dbFiles); + while (obj) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + count += file->entryCount; + obj = objset_next_obj (s_cl5Desc.dbFiles, obj); + } + } + else /* return count for particular db */ + { + /* select correct db file */ + rc = _cl5GetDBFile (replica, &obj); + if (rc == CL5_SUCCESS) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + count = file->entryCount; + object_release (obj); + } + else + { + count = 0; + } + } + + _cl5RemoveThread (); + return count; +} + +/***** Helper Functions *****/ + +/* this call happens under state lock */ +static int _cl5Open (const char *dir, const CL5DBConfig *config, CL5OpenMode openMode) +{ + int rc; + PRBool didRecovery; + + PR_ASSERT (dir); + + /* setup db configuration parameters */ + if (config) + { + _cl5SetDBConfig (config); + } + else + { + _cl5SetDefaultDBConfig (); + } + + /* initialize trimming */ + rc = _cl5TrimInit (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to initialize trimming\n"); + goto done; + } + + /* create the changelog directory if it does not exist */ + rc = cl5CreateDirIfNeeded (dir); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to create changelog directory (%s)\n", dir); + goto done; + } + + s_cl5Desc.dbDir = slapi_ch_strdup (dir); + + /* check database version */ + rc = _cl5CheckDBVersion (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5Open: invalid db version\n"); + goto done; + } + + s_cl5Desc.dbOpenMode = openMode; + + /* initialize db environment */ + rc = _cl5AppInit (&didRecovery); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to initialize db environment\n"); + goto done; + } + + /* open database files */ + rc = _cl5DBOpen (!didRecovery); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Open: failed to open changelog database\n"); + + goto done; + } + +done:; + + if (rc != CL5_SUCCESS) + { + _cl5Close (); + } + + return rc; +} + +int cl5CreateDirIfNeeded (const char *dirName) +{ + int rc; + char buff [MAXPATHLEN + 1]; + char *t; + + PR_ASSERT (dirName); + + rc = PR_Access(dirName, PR_ACCESS_EXISTS); + if (rc == PR_SUCCESS) + { + return CL5_SUCCESS; + } + + /* directory does not exist - try to create */ + strncpy (buff, dirName, MAXPATHLEN); + t = strchr (buff, '/'); + + /* skip first slash */ + if (t) + { + t = strchr (t+1, '/'); + } + + while (t) + { + *t = '\0'; + if (PR_Access (buff, PR_ACCESS_EXISTS) != PR_SUCCESS) + { + rc = PR_MkDir (buff, DIR_CREATE_MODE); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateDirIfNeeded: failed to create dir (%s); NSPR error - %d\n", + dirName, PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + } + + *t++ = FILE_PATHSEP; + + t = strchr (t, '/'); + } + + /* last piece */ + rc = PR_MkDir (buff, DIR_CREATE_MODE); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5CreateDirIfNeeded: failed to create dir; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5RemoveEnv () +{ + DB_ENV *dbEnv = NULL; + int rc = 0; + + if ((rc = db_env_create(&dbEnv, 0)) != 0) + dbEnv = NULL; + + if (dbEnv == NULL) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5RemoveEnv: failed to allocate db environment; " + "db error - %d %s\n", rc, errstr ? errstr : "unknown"); + return CL5_MEMORY_ERROR; + } + rc = dbEnv->remove(dbEnv, s_cl5Desc.dbDir, DB_FORCE); + if (0 != rc) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: failed to remove db environment; " + "db error - %d %s\n", rc, errstr ? errstr : "unknown"); + return CL5_DB_ERROR; + } + return CL5_SUCCESS; +} + +static int _cl5AppInit (PRBool *didRecovery) +{ + int rc; + unsigned int flags = DB_CREATE | DB_INIT_MPOOL | DB_THREAD; + DB_ENV *dbEnv; + if ((rc = db_env_create(&dbEnv, 0)) != 0) + dbEnv = NULL; + + if (dbEnv == NULL) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: failed to allocate db environment; db error - %d (%s)\n", + rc, errstr ? errstr : "unknown"); + return CL5_MEMORY_ERROR; + } + + _cl5InitDBEnv (dbEnv); + + if (didRecovery) + *didRecovery = PR_FALSE; + + /* decide how two open based on the mode in which db is open */ + switch (s_cl5Desc.dbOpenMode) + { + case CL5_OPEN_NORMAL: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG; + /* check if need to initiate recovery */ + rc = _cl5CheckGuardian (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5AppInit: recovering changelog after disorderly shutdown\n"); + flags |= DB_RECOVER; + } + break; + + case CL5_OPEN_RESTORE: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG; + break; + + case CL5_OPEN_CLEAN_RECOVER: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_RECOVER; + break; + + case CL5_OPEN_RESTORE_RECOVER: + flags |= DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_RECOVER_FATAL; + break; + + case CL5_OPEN_LDIF2CL: + /* ONREPL - don't think we need any extra flags here */ + break; + default: + /* fixme? CL5_OPEN_NONE */ + break; + } + + if (!s_cl5Desc.dbConfig.durableTrans) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3200 + dbEnv->set_flags(dbEnv, DB_TXN_NOSYNC, 1); +#else + flags |= DB_TXN_NOSYNC; +#endif + } + + dbEnv->set_errcall(dbEnv, dblayer_log_print); + + /* do recovery if necessary */ + if ((flags & DB_RECOVER) || (flags & DB_RECOVER_FATAL)) + { + if (CL5_OPEN_CLEAN_RECOVER == s_cl5Desc.dbOpenMode) + _cl5RemoveEnv(); + + rc = _cl5Recover (flags, dbEnv); + if (rc != CL5_SUCCESS) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5AppInit: failed to recover changelog; db error - %d %s\n", + rc, errstr ? errstr : "unknown"); + + slapi_ch_free ((void **)&dbEnv); + + return rc; + } + + if (didRecovery) + *didRecovery = PR_TRUE; + flags &= ~(DB_RECOVER | DB_RECOVER_FATAL); + /* Need to reset the env */ + /* Does this leak the dbEnv? */ + if ((rc = db_env_create(&dbEnv, 0)) != 0) + dbEnv = NULL; + + if (dbEnv == NULL) + { + char *errstr = db_strerror(rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: failed to allocate db environment after recovery; " + "db error - %d %s\n", rc, errstr ? errstr : "unknown"); + return CL5_MEMORY_ERROR; + } + _cl5InitDBEnv (dbEnv); + } + + rc = dbEnv->open(dbEnv, s_cl5Desc.dbDir, flags, + s_cl5Desc.dbConfig.fileMode); + if (rc == 0) + { + s_cl5Desc.dbEnv = dbEnv; + s_cl5Desc.dbEnvOpenFlags = flags; + return CL5_SUCCESS; + } + else + { + char *errstr = db_strerror(rc); + char flagstr[20]; + + flagstr[0] = 0; + /* EINVAL return means bad flags - let's see what the flags are */ + if (rc == EINVAL) + { + sprintf(flagstr, "%u", flags); + } + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AppInit: db environment open failed; db error - %d %s %s\n", + rc, errstr ? errstr : "unknown", flagstr); + slapi_ch_free ((void **)&dbEnv); + return CL5_DB_ERROR; + } +} + +static int _cl5DBOpen () +{ + PRBool dbFile; + PRDir *dir; + PRDirEntry *entry = NULL; + int rc; + Object *replica; + + /* create lock that guarantees that each file is only added once to the list */ + s_cl5Desc.fileLock = PR_NewLock (); + + /* loop over all db files and open them; file name format is cl5_<dbid>.<dbext> */ + dir = PR_OpenDir(s_cl5Desc.dbDir); + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DBOpen: failed to open changelog dir; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + + } + + /* initialize set of db file objects */ + s_cl5Desc.dbFiles = objset_new(NULL); + while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == entry->name) + { + break; + } + + dbFile = _cl5FileName2Replica (entry->name, &replica); + if (dbFile) /* this is db file, not a log or dbversion; those are just skipped */ + { + /* we only open files for existing replicas */ + if (replica) + { + rc = _cl5DBOpenFile (replica, NULL /* file object */, + PR_FALSE /* check for duplicates */); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: " + "Error opening file %s\n", + entry->name); + return rc; + } + + object_release (replica); + } + else /* there is no matching replica for the file - remove */ + { + char fullpathname[MAXPATHLEN]; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: " + "file %s has no matching replica; removing\n", entry->name); + + PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name); + if (PR_Delete(fullpathname) != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBOpen: " + "failed to remove (%s) file; NSPR error - %d\n", + entry->name, PR_GetError ()); + + } + } + } + } + + PR_CloseDir(dir); + + return CL5_SUCCESS; +} + +/* this function assumes that the entry was validated + using IsValidOperation + + Data in db format: + ------------------ + <1 byte version><1 byte change_type><sizeof time_t time><null terminated csn> + <null terminated uniqueid><null terminated targetdn> + [<null terminated newrdn><1 byte deleteoldrdn>][<4 byte mod count><mod1><mod2>....] + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte value count> + <4 byte value size><value1><4 byte value size><value2> +*/ +static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len) +{ + int size = 1 /* version */ + 1 /* operation type */ + sizeof (time_t); + char *pos; + PRUint32 t; + slapi_operation_parameters *op; + LDAPMod **add_mods = NULL; + char *rawDN = NULL; + char s[CSN_STRSIZE]; + + PR_ASSERT (entry && entry->op && data && len); + + op = entry->op; + + /* compute size of the buffer needed to hold the data */ + size += CSN_STRSIZE; + size += strlen (op->target_address.uniqueid) + 1; + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid) + size += strlen (op->p.p_add.parentuniqueid) + 1; + else + size ++; /* we just store NULL char */ + slapi_entry2mods (op->p.p_add.target_entry, &rawDN/* dn */, &add_mods); + size += strlen (rawDN) + 1; + size += _cl5GetModsSize (add_mods); + break; + + case SLAPI_OPERATION_MODIFY: size += strlen (op->target_address.dn) + 1; + size += _cl5GetModsSize (op->p.p_modify.modify_mods); + break; + + case SLAPI_OPERATION_MODRDN: size += strlen (op->target_address.dn) + 1; + /* 1 for deleteoldrdn */ + size += strlen (op->p.p_modrdn.modrdn_newrdn) + 2; + if (op->p.p_modrdn.modrdn_newsuperior_address.dn) + size += strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn) + 1; + else + size ++; /* for NULL char */ + if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + size += strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + 1; + else + size ++; /* for NULL char */ + size += _cl5GetModsSize (op->p.p_modrdn.modrdn_mods); + break; + + case SLAPI_OPERATION_DELETE: size += strlen (op->target_address.dn) + 1; + break; + } + + /* allocate data buffer */ + (*data) = (char *) slapi_ch_malloc (size); + if ((*data) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Entry2DBData: failed to allocate data buffer\n"); + return CL5_MEMORY_ERROR; + } + + /* fill in the data buffer */ + pos = *data; + /* write a byte of version */ + (*pos) = V_5; + pos ++; + /* write change type */ + (*pos) = (unsigned char)op->operation_type; + pos ++; + /* write time */ + t = PR_htonl((PRUint32)entry->time); + memcpy (pos, &t, sizeof (t)); + pos += sizeof (t); + /* write csn */ + _cl5WriteString (csn_as_string(op->csn,PR_FALSE,s), &pos); + /* write UniqueID */ + _cl5WriteString (op->target_address.uniqueid, &pos); + + /* figure out what else we need to write depending on the operation type */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: _cl5WriteString (op->p.p_add.parentuniqueid, &pos); + _cl5WriteString (rawDN, &pos); + _cl5WriteMods (add_mods, &pos); + slapi_ch_free ((void**)&rawDN); + ldap_mods_free (add_mods, 1); + break; + + case SLAPI_OPERATION_MODIFY: _cl5WriteString (op->target_address.dn, &pos); + _cl5WriteMods (op->p.p_modify.modify_mods, &pos); + break; + + case SLAPI_OPERATION_MODRDN: _cl5WriteString (op->target_address.dn, &pos); + _cl5WriteString (op->p.p_modrdn.modrdn_newrdn, &pos); + *pos = (PRUint8)op->p.p_modrdn.modrdn_deloldrdn; + pos ++; + _cl5WriteString (op->p.p_modrdn.modrdn_newsuperior_address.dn, &pos); + _cl5WriteString (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, &pos); + _cl5WriteMods (op->p.p_modrdn.modrdn_mods, &pos); + break; + + case SLAPI_OPERATION_DELETE: _cl5WriteString (op->target_address.dn, &pos); + break; + } + + (*len) = size; + + return CL5_SUCCESS; +} + +/* + Data in db format: + ------------------ + <1 byte version><1 byte change_type><sizeof time_t time><null terminated dbid> + <null terminated csn><null terminated uniqueid><null terminated targetdn> + [<null terminated newrdn><1 byte deleteoldrdn>][<4 byte mod count><mod1><mod2>....] + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte value count> + <4 byte value size><value1><4 byte value size><value2> +*/ + + +int +cl5DBData2Entry (const char *data, PRUint32 len, CL5Entry *entry) +{ + int rc; + PRUint8 version; + char *pos = (char *)data; + char *strCSN; + PRUint32 thetime; + slapi_operation_parameters *op; + LDAPMod **add_mods; + char *rawDN; + char s[CSN_STRSIZE]; + + PR_ASSERT (data && entry && entry->op); + + /* ONREPL - check that we do not go beyond the end of the buffer */ + + /* read byte of version */ + version = (PRUint8)(*pos); + if (version != V_5) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5DBData2Entry: invalid data version\n"); + return CL5_BAD_FORMAT; + } + + op = entry->op; + + pos += sizeof(version); + + /* read change type */ + op->operation_type = (PRUint8)(*pos); + pos ++; + + /* need to do the copy first, to skirt around alignment problems on + certain architectures */ + memcpy((char *)&thetime,pos,sizeof(thetime)); + entry->time = (time_t)PR_ntohl(thetime); + pos += sizeof (thetime); + + /* read csn */ + _cl5ReadString (&strCSN, &pos); + if (op->csn == NULL || strcmp (strCSN, csn_as_string(op->csn,PR_FALSE,s)) != 0) + { + op->csn = csn_new_by_string (strCSN); + } + slapi_ch_free ((void**)&strCSN); + + /* read UniqueID */ + _cl5ReadString (&op->target_address.uniqueid, &pos); + + /* figure out what else we need to read depending on the operation type */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: _cl5ReadString (&op->p.p_add.parentuniqueid, &pos); + /* richm: need to free parentuniqueid */ + _cl5ReadString (&rawDN, &pos); + op->target_address.dn = rawDN; + /* convert mods to entry */ + rc = _cl5ReadMods (&add_mods, &pos); + slapi_mods2entry (&(op->p.p_add.target_entry), rawDN, add_mods); + ldap_mods_free (add_mods, 1); + break; + + case SLAPI_OPERATION_MODIFY: _cl5ReadString (&op->target_address.dn, &pos); + rc = _cl5ReadMods (&op->p.p_modify.modify_mods, &pos); + break; + + case SLAPI_OPERATION_MODRDN: _cl5ReadString (&op->target_address.dn, &pos); + _cl5ReadString (&op->p.p_modrdn.modrdn_newrdn, &pos); + op->p.p_modrdn.modrdn_deloldrdn = *pos; + pos ++; + _cl5ReadString (&op->p.p_modrdn.modrdn_newsuperior_address.dn, &pos); + _cl5ReadString (&op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, &pos); + rc = _cl5ReadMods (&op->p.p_modrdn.modrdn_mods, &pos); + break; + + case SLAPI_OPERATION_DELETE: _cl5ReadString (&op->target_address.dn, &pos); + rc = CL5_SUCCESS; + break; + + default: rc = CL5_BAD_FORMAT; + slapi_log_error(SLAPI_LOG_FATAL, + repl_plugin_name_cl, + "cl5DBData2Entry: failed to format entry\n"); + break; + } + + return rc; +} + +/* thread management functions */ +static int _cl5DispatchDBThreads () +{ + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5DeadlockMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create deadlock thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5CheckpointMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create checkpoint thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void *)_cl5TrickleMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE) ) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create trickle thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (NULL == PR_CreateThread (PR_USER_THREAD, (VFP)(void*)_cl5TrimMain, + NULL, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, DEFAULT_THREAD_STACKSIZE) ) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DispatchDBThreads: failed to create trimming thread; " + "NSPR error - %d\n", PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5AddThread () +{ + /* lock the state lock so that nobody can change the state + while backup is in progress + */ + PR_RWLock_Rlock (s_cl5Desc.stLock); + + /* open changelog if it is not already open */ + if (s_cl5Desc.dbState != CL5_STATE_OPEN) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5AddThread: invalid changelog state - %d\n", s_cl5Desc.dbState); + PR_RWLock_Unlock (s_cl5Desc.stLock); + return CL5_BAD_STATE; + } + + /* increment global thread count to make sure that changelog does not close while + backup is in progress */ + PR_AtomicIncrement (&s_cl5Desc.threadCount); + + PR_RWLock_Unlock (s_cl5Desc.stLock); + + return CL5_SUCCESS; +} + +static void _cl5RemoveThread () +{ + PR_ASSERT (s_cl5Desc.threadCount > 0); + PR_AtomicDecrement (&s_cl5Desc.threadCount); +} + +/* data conversion functions */ +static void _cl5WriteString (const char *str, char **buff) +{ + if (str) + { + strcpy (*buff, str); + (*buff) += strlen (str) + 1; + } + else /* just write NULL char */ + { + (**buff) = '\0'; + (*buff) ++; + } +} + +static void _cl5ReadString (char **str, char **buff) +{ + if (str) + { + int len = strlen (*buff); + + if (len) + { + *str = slapi_ch_strdup (*buff); + (*buff) += len + 1; + } + else /* just null char - skip it */ + { + *str = NULL; + (*buff) ++; + } + } + else /* just skip this string */ + { + (*buff) += strlen (*buff) + 1; + } +} + +/* mods format: + ----------- + <4 byte mods count><mod1><mod2>... + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte count> + <4 byte size><value1><4 byte size><value2>... + */ +static void _cl5WriteMods (LDAPMod **mods, char **buff) +{ + PRInt32 i; + char *mod_start; + PRInt32 count; + + if (mods == NULL) + return; + + /* skip mods count */ + mod_start = (*buff) + sizeof (count); + + /* write mods*/ + for (i=0; mods[i]; i++) + { + _cl5WriteMod (mods[i], &mod_start); + } + + count = PR_htonl(i); + memcpy (*buff, &count, sizeof (count)); + + (*buff) = mod_start; +} + +static void _cl5WriteMod (LDAPMod *mod, char **buff) +{ + char *pos; + PRInt32 count; + struct berval *bv; + Slapi_Mod smod; + + slapi_mod_init_byref(&smod, mod); + + pos = *buff; + /* write mod op */ + *pos = (PRUint8)slapi_mod_get_operation (&smod); + pos ++; + /* write attribute name */ + _cl5WriteString (slapi_mod_get_type (&smod), &pos); + + /* write value count */ + count = PR_htonl(slapi_mod_get_num_values(&smod)); + memcpy (pos, &count, sizeof (count)); + pos += sizeof (PRInt32); + + bv = slapi_mod_get_first_value (&smod); + while (bv) + { + _cl5WriteBerval (bv, &pos); + bv = slapi_mod_get_next_value (&smod); + } + + (*buff) = pos; + + slapi_mod_done (&smod); +} + +/* mods format: + ----------- + <4 byte mods count><mod1><mod2>... + + mod format: + ----------- + <1 byte modop><null terminated attr name><4 byte count> + {<4 byte size><value1><4 byte size><value2>... || + <null terminated str1> <null terminated str2>...} + */ + +static int _cl5ReadMods (LDAPMod ***mods, char **buff) +{ + char *pos = *buff; + int i; + int rc; + PRInt32 mod_count; + Slapi_Mods smods; + Slapi_Mod smod; + + /* need to copy first, to skirt around alignment problems on certain + architectures */ + memcpy((char *)&mod_count,*buff,sizeof(mod_count)); + mod_count = PR_ntohl(mod_count); + pos += sizeof (mod_count); + + slapi_mods_init (&smods , mod_count); + + for (i = 0; i < mod_count; i++) + { + rc = _cl5ReadMod (&smod, &pos); + if (rc != CL5_SUCCESS) + { + slapi_mods_done(&smods); + return rc; + } + + slapi_mods_add_smod(&smods, &smod); + } + + *buff = pos; + + *mods = slapi_mods_get_ldapmods_passout (&smods); + slapi_mods_done(&smods); + + return CL5_SUCCESS; +} + +static int _cl5ReadMod (Slapi_Mod *smod, char **buff) +{ + char *pos = *buff; + int i; + PRInt32 val_count; + char *type; + int op; + struct berval bv; + + op = (*pos) & 0x000000FF; + pos ++; + _cl5ReadString (&type, &pos); + + /* need to do the copy first, to skirt around alignment problems on + certain architectures */ + memcpy((char *)&val_count,pos,sizeof(val_count)); + val_count = PR_ntohl(val_count); + pos += sizeof (PRInt32); + + slapi_mod_init(smod, val_count); + slapi_mod_set_operation (smod, op|LDAP_MOD_BVALUES); + slapi_mod_set_type (smod, type); + slapi_ch_free ((void**)&type); + + for (i = 0; i < val_count; i++) + { + _cl5ReadBerval (&bv, &pos); + slapi_mod_add_value (smod, &bv); + slapi_ch_free((void **) &bv.bv_val); + } + + (*buff) = pos; + + return CL5_SUCCESS; +} + +static int _cl5GetModsSize (LDAPMod **mods) +{ + int size; + int i; + + if (mods == NULL) + return 0; + + size = sizeof (PRInt32); + for (i=0; mods[i]; i++) + { + size += _cl5GetModSize (mods[i]); + } + + return size; +} + +static int _cl5GetModSize (LDAPMod *mod) +{ + int size; + int i; + + size = 1 + strlen (mod->mod_type) + 1 + sizeof (mod->mod_op); + i = 0; + if (mod->mod_op & LDAP_MOD_BVALUES) /* values are in binary form */ + { + while (mod->mod_bvalues != NULL && mod->mod_bvalues[i] != NULL) + { + size += mod->mod_bvalues[i]->bv_len + sizeof (mod->mod_bvalues[i]->bv_len); + i++; + } + } + else /* string data */ + { + PR_ASSERT(0); /* ggood string values should never be used in the server */ + } + + return size; +} + +static void _cl5ReadBerval (struct berval *bv, char** buff) +{ + PRUint32 length = 0; + PRUint32 net_length = 0; + + PR_ASSERT (bv && buff); + + /***PINAKI need to do the copy first, to skirt around alignment problems on + certain architectures */ + /* DBDB : struct berval.bv_len is defined as unsigned long + * But code here expects it to be 32-bits in size. + * On 64-bit machines, this is not the case. + * I changed the code to consistently use 32-bit (4-byte) + * values on the encoded side. This means that it's + * possible to generate a huge berval that will not + * be encoded properly. However, this seems unlikely + * to happen in reality, and I felt that retaining the + * old on-disk format for the changely in the 64-bit + * version of the server was important. + */ + + memcpy((char *)&net_length, *buff, sizeof(net_length)); + length = PR_ntohl(net_length); + *buff += sizeof(net_length); + bv->bv_len = length; + + if (bv->bv_len > 0) { + bv->bv_val = (char*)slapi_ch_malloc (bv->bv_len); + memcpy (bv->bv_val, *buff, bv->bv_len); + *buff += bv->bv_len; + } + else { + bv->bv_val = NULL; + } +} + +static void _cl5WriteBerval (struct berval *bv, char** buff) +{ + PRUint32 length = 0; + PRUint32 net_length = 0; + + length = (PRUint32) bv->bv_len; + net_length = PR_htonl(length); + + memcpy(*buff, &net_length, sizeof (net_length)); + *buff += sizeof (net_length); + memcpy (*buff, bv->bv_val, length); + *buff += length; +} + +/* data format: <value count> <value size> <value> <value size> <value> ..... */ +static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size) +{ + PRInt32 count; + int i; + char *pos; + + PR_ASSERT (bv && buff); + + /* ONREPL - need to check that we don't go beyond the end of the buffer */ + + pos = *buff; + memcpy((char *)&count, pos, sizeof(count)); + count = PR_htonl (count); + pos += sizeof(count); + + /* allocate bervals */ + *bv = (struct berval **)slapi_ch_malloc ((count + 1) * sizeof (struct berval*)); + if (*bv == NULL) + { + return CL5_MEMORY_ERROR; + } + + for (i = 0; i < count; i++) + { + (*bv)[i] = (struct berval *)slapi_ch_malloc (sizeof (struct berval)); + if ((*bv)[i] == NULL) + { + ber_bvecfree(*bv); + return CL5_MEMORY_ERROR; + } + + _cl5ReadBerval ((*bv)[i], &pos); + } + + (*bv)[count] = NULL; + *buff = pos; + + return CL5_SUCCESS; +} + +/* data format: <value count> <value size> <value> <value size> <value> ..... */ +static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size) +{ + PRInt32 count, net_count; + char *pos; + int i; + + PR_ASSERT (bv && buff && size); + + /* compute number of values and size of the buffer to hold them */ + *size = sizeof (count); + for (count = 0; bv[count]; count ++) + { + *size += sizeof (bv[count]->bv_len) + bv[count]->bv_len; + } + + /* allocate buffer */ + *buff = (char*) slapi_ch_malloc (*size); + if (*buff == NULL) + { + *size = 0; + return CL5_MEMORY_ERROR; + } + + /* fill the buffer */ + pos = *buff; + net_count = PR_htonl(count); + memcpy (pos, &net_count, sizeof (net_count)); + pos += sizeof (net_count); + for (i = 0; i < count; i ++) + { + _cl5WriteBerval (bv[i], &pos); + } + + return CL5_SUCCESS; +} + +static int _cl5DeadlockMain (void *param) +{ + PRIntervalTime interval; + int rc; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + interval = PR_MillisecondsToInterval(100); + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + int aborted; + if ((rc = LOCK_DETECT(s_cl5Desc.dbEnv, 0, DB_LOCK_YOUNGEST, &aborted)) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5DeadlockMain: lock_detect failed (%d transaction%s aborted); db error - %d %s\n", + aborted, (aborted == 1)? "":"s", rc, db_strerror(rc)); + } + else if (aborted) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5DeadlockMain: lock_detect succeeded, but %d transaction%s ha%s been aborted\n", + aborted, (aborted == 1)? "":"s", (aborted == 1)? "s":"ve"); + } + + DS_Sleep(interval); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + return 0; +} + +static int _cl5CheckpointMain (void *param) +{ + time_t lastCheckpointCompletion = 0; + PRIntervalTime interval; + int rc = -1; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + + interval = PR_MillisecondsToInterval(1000); + lastCheckpointCompletion = current_time(); + + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + /* Check to see if the checkpoint interval has elapsed */ + if (current_time() - lastCheckpointCompletion > s_cl5Desc.dbConfig.checkpointInterval) + { + rc = TXN_CHECKPOINT(s_cl5Desc.dbEnv, 0, 0, 0); + if (rc == 0) + { + lastCheckpointCompletion = current_time(); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + else if (rc != DB_INCOMPLETE) /* real error happened */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckpointMain: checkpoint failed, db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + + /* According to dboreham, we are doing checkpoint twice + to reduce the number of transaction log files which need + to be retained at any time. */ + rc = TXN_CHECKPOINT(s_cl5Desc.dbEnv, 0, 0, 0); + if (rc == 0) + { + lastCheckpointCompletion = current_time(); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + else if (rc != DB_INCOMPLETE) /* real error happened */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckpointMain: checkpoint failed, db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + + /* check if we should truncate logs */ + if (s_cl5Desc.dbConfig.circularLogging) + { + char **list = NULL; + char **listp = NULL; + int rc = -1; + char filename[MAXPATHLEN + 1]; + + /* find out which log files don't contain active txns */ + rc = LOG_ARCHIVE(s_cl5Desc.dbEnv, &list, 0, malloc); + if (0 == rc && NULL != list) + { + /* zap 'em ! */ + for (listp = list; *listp != NULL; ++listp) + { + PR_snprintf(filename, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir,*listp); + PR_Delete (filename); + } + slapi_ch_free((void **)&list); + } + } + } + + /* sleep for a while */ + /* why aren't we sleeping exactly the right amount of time ? */ + /* answer---because the interval might be changed after the server starts up */ + DS_Sleep(interval); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + return 0; +} + +static int _cl5TrickleMain (void *param) +{ + PRIntervalTime interval; + int pages_written; + int rc; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + interval = PR_MillisecondsToInterval(1000); + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + if ((rc = MEMP_TRICKLE(s_cl5Desc.dbEnv, + s_cl5Desc.dbConfig.tricklePercentage, &pages_written)) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrickleMain: memp_trickle failed; db error - %d %s\n", + rc, db_strerror(rc)); + } + + DS_Sleep(interval); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + + return 0; +} + +/* upgrade from db33 to db41 + * 1. Run recovery on the database environment using the DB_ENV->open method + * 2. Remove any Berkeley DB environment using the DB_ENV->remove method + * 3. extention .db3 -> .db4 ### koko kara !!! + */ +static int _cl5Upgrade3_4(char *fromVersion, char *toVersion) +{ + PRDir *dir = NULL; + PRDirEntry *entry = NULL; + DB *thisdb = NULL; + CL5OpenMode backup; + int rc = 0; + + backup = s_cl5Desc.dbOpenMode; + s_cl5Desc.dbOpenMode = CL5_OPEN_CLEAN_RECOVER; + /* CL5_OPEN_CLEAN_RECOVER does 1 and 2 */ + rc = _cl5AppInit (NULL); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to open the db env\n"); + return rc; + } + + dir = PR_OpenDir(s_cl5Desc.dbDir); + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to open changelog dir %s; NSPR error - %d\n", + s_cl5Desc.dbDir, PR_GetError ()); + goto out; + } + + while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == entry->name) + { + break; + } + if (_cl5FileEndsWith(entry->name, DB_EXTENSION_DB3)) + { + char oName [MAXPATHLEN + 1]; + char nName [MAXPATHLEN + 1]; + char *p = NULL; + char c; + int baselen = 0; + PR_snprintf(oName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name); + p = strstr(oName, DB_EXTENSION_DB3); + if (NULL == p) + { + continue; + } + /* db->rename closes DB; need to create every time */ + rc = db_create(&thisdb, s_cl5Desc.dbEnv, 0); + if (0 != rc) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to get db handle\n"); + goto out; + } + + baselen = p - oName; + c = *p; + *p = '\0'; + PR_snprintf(nName, MAXPATHLEN+1, "%s", oName); + PR_snprintf(nName + baselen, MAXPATHLEN+1-baselen, "%s", DB_EXTENSION); + *p = c; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Upgrade3_4: renaming %s to %s\n", oName, nName); + rc = thisdb->rename(thisdb, (const char *)oName, NULL /* subdb */, + (const char *)nName, 0); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Upgrade3_4: failed to rename file (%s -> %s); " + "db error - %d %s\n", oName, nName, rc, db_strerror(rc)); + break; + } + } + } + /* update the version file */ + _cl5WriteDBVersion (); + + /* update the guardian file */ + _cl5WriteGuardian (); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Upgrading from %s to %s is successfully done (%s)\n", + fromVersion, toVersion, s_cl5Desc.dbDir); +out: + if (NULL != dir) + { + PR_CloseDir(dir); + } + if (s_cl5Desc.dbEnv) + { + DB_ENV *dbEnv = s_cl5Desc.dbEnv; + dbEnv->close(dbEnv, 0); + s_cl5Desc.dbEnv = NULL; + } + return rc; +} + +static int _cl5CheckDBVersion () +{ + char clVersion [VERSION_SIZE + 1]; + char dbVersion [VERSION_SIZE + 1]; + int rc; + + if (!cl5Exist (s_cl5Desc.dbDir)) + { + /* this is new changelog - write DB version and guardian file */ + rc = _cl5WriteDBVersion (); + if (rc == CL5_SUCCESS) { + rc = _cl5WriteGuardian(); + } + } + else + { + PR_snprintf (clVersion, VERSION_SIZE, "%s/%s/%s", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + rc = _cl5ReadDBVersion (s_cl5Desc.dbDir, dbVersion); + + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckDBVersion: invalid dbversion\n"); + rc = CL5_BAD_DBVERSION; + } + else if (strcasecmp (clVersion, dbVersion) != 0) + { + char prevClVersion [VERSION_SIZE + 1]; + PR_snprintf (prevClVersion, VERSION_SIZE, "%s/%s/%s", + CL5_TYPE, REPL_PLUGIN_NAME, CHANGELOG_DB_VERSION_PREV); + if (strcasecmp (prevClVersion, dbVersion) == 0) + { + /* upgrade */ + rc = _cl5Upgrade3_4(prevClVersion, clVersion); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckDBVersion: upgrade %s -> %s failed\n", + CHANGELOG_DB_VERSION_PREV, CHANGELOG_DB_VERSION); + rc = CL5_BAD_DBVERSION; + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CheckDBVersion: invalid dbversion\n"); + rc = CL5_BAD_DBVERSION; + } + } + + } + + return rc; +} + +static int _cl5ReadDBVersion (const char *dir, char *clVersion) +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + char buff [BUFSIZ]; + PRInt32 size; + char *tok; + char * iter = NULL; + + if (clVersion) + { + clVersion [0] = '\0'; + } + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", dir, VERSION_FILE); + + file = PR_Open (fName, PR_RDONLY, 777); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadDBVersion: failed to open DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + size = slapi_read_buffer (file, buff, BUFSIZ); + if (size < 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadDBVersion: failed to read DBVERSION; NSPR error - %d\n", + PR_GetError ()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + /* parse the data */ + buff[size]= '\0'; + tok = ldap_utf8strtok_r (buff, "\n", &iter); + if (tok) + { + if (clVersion) + { + strcpy(clVersion, tok); + } + } + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadDBVersion: failed to close DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5WriteDBVersion () +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + char clVersion [VERSION_SIZE + 1]; + PRInt32 len, size; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, VERSION_FILE); + + file = PR_Open (fName, PR_WRONLY | PR_CREATE_FILE, s_cl5Desc.dbConfig.fileMode); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteDBVersion: failed to open DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + /* write changelog version */ + PR_snprintf (clVersion, VERSION_SIZE, "%s/%s/%s\n", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + + len = strlen (clVersion); + size = slapi_write_buffer (file, clVersion, len); + if (size != len) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteDBVersion: failed to write DBVERSION; NSPR error - %d\n", + PR_GetError ()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteDBVersion: failed to close DBVERSION; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +/* for now guardian file is just like dbversion file */ +static int _cl5CheckGuardian () +{ + char plVersion [VERSION_SIZE + 1]; + char dbVersion [VERSION_SIZE + 1]; + int rc; + + /* new changelog - no guardian file */ + if (!cl5Exist(s_cl5Desc.dbDir)) + { + return CL5_SUCCESS; + } + else + { + PR_snprintf (plVersion, VERSION_SIZE, "%s/%s/%s", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + rc = _cl5ReadGuardian (dbVersion); + + if (rc != CL5_SUCCESS || strcasecmp (plVersion, dbVersion) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5CheckGuardian: missing or invalid guardian file\n"); + return (CL5_BAD_FORMAT); + } + + /* remove guardian file */ + rc = _cl5RemoveGuardian (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5CheckGuardian: failed to remove guardian file\n"); + } + } + + return rc; +} + +static int _cl5WriteGuardian () +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + char version [VERSION_SIZE]; + PRInt32 len, size; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE); + + file = PR_Open (fName, PR_WRONLY | PR_CREATE_FILE, s_cl5Desc.dbConfig.fileMode); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteGuardian: failed to open guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + PR_snprintf (version, VERSION_SIZE, "%s/%s/%s\n", CL5_TYPE, REPL_PLUGIN_NAME, + CHANGELOG_DB_VERSION); + + len = strlen (version); + size = slapi_write_buffer (file, version, len); + if (size != len) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteGuardian: failed to write guardian file; NSPR error - %d\n", + PR_GetError()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteGuardian: failed to close guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5ReadGuardian (char *buff) +{ + int rc; + PRFileDesc *file; + char fName [MAXPATHLEN + 1]; + PRInt32 size; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE); + + file = PR_Open (fName, PR_RDONLY, 0); + if (file == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadGuardian: failed to open guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + size = slapi_read_buffer (file, buff, VERSION_SIZE); + if (size <= 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadGuardian: failed to read guardian file; NSPR error - %d\n", + PR_GetError()); + PR_Close (file); + return CL5_SYSTEM_ERROR; + } + + buff [size-1] = '\0'; + + rc = PR_Close (file); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5ReadGuardian: failed to close guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5RemoveGuardian () +{ + char fName [MAXPATHLEN + 1]; + int rc; + + PR_snprintf (fName, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, GUARDIAN_FILE); + + rc = PR_Delete (fName); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5RemoveGuardian: failed to remove guardian file; NSPR error - %d\n", + PR_GetError()); + return CL5_SYSTEM_ERROR; + } + + return CL5_SUCCESS; +} + +/* must be called under the state lock */ +static void _cl5Close () +{ + int rc2 = 0; + PRIntervalTime interval; + + if (s_cl5Desc.dbState != CL5_STATE_CLOSED) /* Don't try to close twice */ + { + + /* close db files */ + _cl5DBClose (); + + /* stop global threads */ + interval = PR_MillisecondsToInterval(100); + while (s_cl5Desc.threadCount > 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "_cl5Close: waiting for threads to exit: %d thread(s) still active\n", + s_cl5Desc.threadCount); + DS_Sleep(interval); + } + + /* cleanup trimming */ + _cl5TrimCleanup (); + + /* shutdown db environment */ + if (s_cl5Desc.dbEnv) + { + DB_ENV *dbEnv = s_cl5Desc.dbEnv; + rc2 = dbEnv->close(dbEnv, 0); + s_cl5Desc.dbEnv = NULL; + } + + /* record successful close by writing guardian file; + we do it in all case accept incomplete open due to an error */ + if (s_cl5Desc.dbState == CL5_STATE_CLOSING || s_cl5Desc.dbOpenMode != CL5_OPEN_NORMAL) + { + _cl5WriteGuardian (); + } + + /* remove changelog if requested */ + if (s_cl5Desc.dbRmOnClose) + { + + if (_cl5Delete (s_cl5Desc.dbDir, 1) != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5Close: failed to remove changelog\n"); + } + s_cl5Desc.dbRmOnClose = PR_FALSE; + } + + slapi_ch_free ((void **)&s_cl5Desc.dbDir); + memset (&s_cl5Desc.dbConfig, 0, sizeof (s_cl5Desc.dbConfig)); + s_cl5Desc.fatalError = PR_FALSE; + s_cl5Desc.threadCount = 0; + s_cl5Desc.dbOpenMode = CL5_OPEN_NONE; + } +} + +static void _cl5DBClose () +{ + if (NULL != s_cl5Desc.dbFiles) + { + objset_delete (&s_cl5Desc.dbFiles); + } + if (NULL != s_cl5Desc.fileLock) + { + PR_DestroyLock (s_cl5Desc.fileLock); + } +} + +/* state lock must be locked */ +static int _cl5Delete (const char *clDir, int rmDir) +{ + PRDir *dir; + char filename[MAXPATHLEN + 1]; + PRDirEntry *entry = NULL; + int rc; + + /* remove all files in the directory and the directory */ + dir = PR_OpenDir(clDir); + if (dir == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to open changelog dir; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + + } + + while (NULL != (entry = PR_ReadDir(dir, PR_SKIP_DOT | PR_SKIP_DOT_DOT))) + { + if (NULL == entry->name) + { + break; + } + PR_snprintf(filename, MAXPATHLEN, "%s/%s", clDir, entry->name); + rc = PR_Delete(filename); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to remove (%s) file; NSPR error - %d\n", + filename, PR_GetError ()); + } + } + + rc = PR_CloseDir(dir); + if (rc != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to close changelog dir (%s); NSPR error - %d\n", + clDir, PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + + if (rmDir) + { + rc = PR_RmDir (clDir); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5Delete: failed to remove changelog dir (%s); errno = %d\n", + clDir, errno); + return CL5_SYSTEM_ERROR; + } + } + + return CL5_SUCCESS; +} + +static void _cl5SetDefaultDBConfig () +{ + s_cl5Desc.dbConfig.cacheSize = CL5_DEFAULT_CONFIG_DB_DBCACHESIZE; + s_cl5Desc.dbConfig.durableTrans = CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS; + s_cl5Desc.dbConfig.checkpointInterval = CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL; + s_cl5Desc.dbConfig.circularLogging = CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING; + s_cl5Desc.dbConfig.pageSize = CL5_DEFAULT_CONFIG_DB_PAGE_SIZE; + s_cl5Desc.dbConfig.logfileSize = CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE; + s_cl5Desc.dbConfig.maxTxnSize = CL5_DEFAULT_CONFIG_DB_TXN_MAX; + s_cl5Desc.dbConfig.verbose = CL5_DEFAULT_CONFIG_DB_VERBOSE; + s_cl5Desc.dbConfig.debug = CL5_DEFAULT_CONFIG_DB_DEBUG; + s_cl5Desc.dbConfig.tricklePercentage = CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE; + s_cl5Desc.dbConfig.spinCount = CL5_DEFAULT_CONFIG_DB_SPINCOUNT; + s_cl5Desc.dbConfig.nb_lock_config = CL5_DEFAULT_CONFIG_NB_LOCK; + s_cl5Desc.dbConfig.fileMode = FILE_CREATE_MODE; +} + +static void _cl5SetDBConfig (const CL5DBConfig *config) +{ + /* through CL5DBConfig, we have access to all the LDAP configurable Changelog DB parameters */ + s_cl5Desc.dbConfig.cacheSize = config->cacheSize; + s_cl5Desc.dbConfig.durableTrans = config->durableTrans; + s_cl5Desc.dbConfig.checkpointInterval = config->checkpointInterval; + s_cl5Desc.dbConfig.circularLogging = config->circularLogging; + s_cl5Desc.dbConfig.pageSize = config->pageSize; + s_cl5Desc.dbConfig.logfileSize = config->logfileSize; + s_cl5Desc.dbConfig.maxTxnSize = config->maxTxnSize; + s_cl5Desc.dbConfig.verbose = config->verbose; + s_cl5Desc.dbConfig.debug = config->debug; + s_cl5Desc.dbConfig.tricklePercentage = config->tricklePercentage; + s_cl5Desc.dbConfig.spinCount = config->spinCount; + s_cl5Desc.dbConfig.nb_lock_config = config->nb_lock_config; + s_cl5Desc.dbConfig.maxConcurrentWrites = config->maxConcurrentWrites; + + if (config->spinCount != 0) + { + DB_ENV_SET_TAS_SPINS(s_cl5Desc.dbEnv, config->spinCount); + } + + /* Some other configuration parameters are hardcoded... */ + s_cl5Desc.dbConfig.fileMode = FILE_CREATE_MODE; +} + +#define ONEG 1073741824 /* one giga bytes */ +static void _cl5InitDBEnv(DB_ENV *dbEnv) +{ + dbEnv->set_errpfx(dbEnv, "ns-slapd"); + dbEnv->set_lg_max(dbEnv, s_cl5Desc.dbConfig.logfileSize); + dbEnv->set_tx_max(dbEnv, s_cl5Desc.dbConfig.maxTxnSize); + dbEnv->set_cachesize(dbEnv, s_cl5Desc.dbConfig.cacheSize/ONEG, + s_cl5Desc.dbConfig.cacheSize%ONEG, + 0); + /* Set default number of locks */ + dbEnv->set_lk_max_locks(dbEnv, s_cl5Desc.dbConfig.nb_lock_config); + + if (s_cl5Desc.dbConfig.verbose) + { + int on = 1; + dbEnv->set_verbose(dbEnv, DB_VERB_CHKPOINT, on); + dbEnv->set_verbose(dbEnv, DB_VERB_DEADLOCK, on); + dbEnv->set_verbose(dbEnv, DB_VERB_RECOVERY, on); + dbEnv->set_verbose(dbEnv, DB_VERB_WAITSFOR, on); + } + if (s_cl5Desc.dbConfig.debug) + { + dbEnv->set_errcall(dbEnv, _cl5DBLogPrint); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300 + dbEnv->set_alloc(dbEnv, malloc, realloc, free); +#endif +} + +static void _cl5DBLogPrint(const char* prefix, char *buffer) +{ + /* We ignore the prefix since we know who we are anyway */ + slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name_cl, "cl5: %s\n", buffer); +} + +static PRBool _cl5IsLogFile (const char *path) +{ + int rc; + + /* Is the filename at least 4 characters long ? */ + if (strlen(path) < 4) + { + return PR_FALSE; /* Not a log file then */ + } + + /* Are the first 4 characters "log." ? */ + rc = strncmp(path,"log.",4); + if (0 == rc) + { + /* Now, are the last 4 characters _not_ .db# ? */ + const char *piece = path + (strlen(path) - 4); + rc = strcmp(piece, DB_EXTENSION); + if (0 != rc) + { + /* Is */ + return PR_TRUE; + } + } + return PR_FALSE; /* Is not */ +} + +static int _cl5Recover (int open_flags, DB_ENV *dbEnv) +{ + /* If we're doing recovery, we MUST open the env single-threaded ! */ + int recover_flags = open_flags & ~DB_THREAD; + int rc; + + rc = dbEnv->open(dbEnv, s_cl5Desc.dbDir, recover_flags, s_cl5Desc.dbConfig.fileMode); + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Recover: appinit failed; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } + + /* Now close it so we can re-open it again... */ + dbEnv->close(dbEnv, 0); + + return CL5_SUCCESS; +} + +/* Trimming helper functions */ +static int _cl5TrimInit () +{ + /* just create the lock while we are singlethreaded */ + s_cl5Desc.dbTrim.lock = PR_NewLock(); + + if (s_cl5Desc.dbTrim.lock == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5InitTrimming: failed to create lock; NSPR error - %d\n", + PR_GetError ()); + return CL5_SYSTEM_ERROR; + } + else + { + return CL5_SUCCESS; + } +} + +static void _cl5TrimCleanup () +{ + if (s_cl5Desc.dbTrim.lock) + PR_DestroyLock (s_cl5Desc.dbTrim.lock); + + memset (&s_cl5Desc.dbTrim, 0, sizeof (s_cl5Desc.dbTrim)); +} + +static int _cl5TrimMain (void *param) +{ + PRIntervalTime interval; + time_t timePrev = current_time (); + time_t timeNow; + + PR_AtomicIncrement (&s_cl5Desc.threadCount); + interval = PR_SecondsToInterval(CHANGELOGDB_TRIM_INTERVAL); + + while (s_cl5Desc.dbState != CL5_STATE_CLOSING) + { + timeNow = current_time (); + if (timeNow - timePrev >= CHANGELOGDB_TRIM_INTERVAL) + { + /* time to trim */ + timePrev = timeNow; + _cl5DoTrimming (); + } + if (NULL == s_cl5Desc.clLock) + { + /* most likely, emergency */ + break; + } + + PR_Lock(s_cl5Desc.clLock); + PR_WaitCondVar(s_cl5Desc.clCvar, interval); + PR_Unlock(s_cl5Desc.clLock); + } + + PR_AtomicDecrement (&s_cl5Desc.threadCount); + + return 0; +} + +/* We remove an entry if it has been replayed to all consumers and + and the number of entries in the changelog is larger than maxEntries + or age of the entry is larger than maxAge. + Also we can't purge entries which correspond to max csns in the + supplier's ruv. Here is a example where we can get into trouble: + The server is setup with time based trimming and no consumer's + At some point all the entries are trimmed from the changelog. + At a later point a consumer is added and initialized online + Then a change is made on the supplier. + To update the consumer, the supplier would attempt to locate + the last change sent to the consumer in the changelog and will + fail because the change was removed. + + */ + +static void _cl5DoTrimming () +{ + Object *obj; + long numToTrim; + + PR_Lock (s_cl5Desc.dbTrim.lock); + + /* ONREPL We trim file by file which means that some files will be + trimmed more often than other. We might have to fix that by, for + example, randomizing starting point */ + obj = objset_first_obj (s_cl5Desc.dbFiles); + while (obj && _cl5CanTrim ((time_t)0, &numToTrim)) + { + _cl5TrimFile (obj, &numToTrim); + obj = objset_next_obj (s_cl5Desc.dbFiles, obj); + } + + if (obj) + object_release (obj); + + PR_Unlock (s_cl5Desc.dbTrim.lock); + + return; +} + +/* Note that each file contains changes for a single replicated area. + trimming algorithm: +*/ +#define CL5_TRIM_MAX_PER_TRANSACTION 10 + +static void _cl5TrimFile (Object *obj, long *numToTrim) +{ + DB_TXN *txnid; + RUV *ruv = NULL; + CL5Entry entry; + slapi_operation_parameters op = {0}; + void *it; + int finished = 0, totalTrimmed = 0, count; + PRBool abort; + char strCSN[CSN_STRSIZE]; + int rc; + + PR_ASSERT (obj); + + /* construct the ruv up to which we can purge */ + rc = _cl5GetRUV2Purge2 (obj, &ruv); + if (rc != CL5_SUCCESS || ruv == NULL) + { + return; + } + + entry.op = &op; + + while ( !finished && !g_get_shutdown() ) + { + it = NULL; + count = 0; + txnid = NULL; + abort = PR_FALSE; + + /* DB txn lock accessed pages until the end of the transaction. */ + + rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrimFile: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + finished = PR_TRUE; + break; + } + + finished = _cl5GetFirstEntry (obj, &entry, &it, txnid); + while ( !finished ) + { + /* + * This change can be trimmed if it exceeds purge + * parameters and has been seen by all consumers. + */ + if ( (*numToTrim > 0 || _cl5CanTrim (entry.time, numToTrim)) && + ruv_covers_csn_strict (ruv, op.csn) ) + { + rc = _cl5CurrentDeleteEntry (it); + if ( rc == CL5_SUCCESS ) + { + /* update purge vector */ + rc = _cl5UpdateRUV (obj, op.csn, PR_FALSE, PR_TRUE); + } + if ( rc == CL5_SUCCESS) + { + if (*numToTrim > 0) (*numToTrim)--; + count++; + } + else + { + /* The above two functions have logged the error */ + abort = PR_TRUE; + } + + } + else + { + /* The changelog DB is time ordered. If we can not trim + * a CSN, we will not be allowed to trim the rest of the + * CSNs generally. However, the maxcsn of each replica ID + * is always kept in the changelog as an anchor for + * replaying future changes. We have to skip those anchor + * CSNs, otherwise a non-active replica ID could block + * the trim forever. + */ + CSN *maxcsn = NULL; + ReplicaId rid; + + rid = csn_get_replicaid (op.csn); + ruv_get_largest_csn_for_replica (ruv, rid, &maxcsn); + if ( csn_compare (op.csn, maxcsn) != 0 ) + { + /* op.csn is not anchor CSN */ + finished = 1; + } + else + { + slapi_log_error (SLAPI_LOG_REPL, NULL, + "Changelog purge skipped anchor csn %s\n", + csn_as_string (maxcsn, PR_FALSE, strCSN)); + + /* extra read to skip the current record */ + cl5_operation_parameters_done (&op); + finished =_cl5GetNextEntry (&entry, it); + } + if (maxcsn) csn_free (&maxcsn); + } + cl5_operation_parameters_done (&op); + if (finished || abort || count >= CL5_TRIM_MAX_PER_TRANSACTION) + { + /* If we reach CL5_TRIM_MAX_PER_TRANSACTION, + * we close the cursor, + * commit the transaction and restart a new transaction + */ + break; + } + finished = _cl5GetNextEntry (&entry, it); + } + + /* MAB: We need to close the cursor BEFORE the txn commits/aborts. + * If we don't respect this order, we'll screw up the database, + * placing it in DB_RUNRECOVERY mode + */ + cl5DestroyIterator (it); + + if (abort) + { + finished = 1; + rc = TXN_ABORT (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrimFile: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } + } + else + { + rc = TXN_COMMIT (txnid, 0); + if (rc != 0) + { + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5TrimFile: failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } + else + { + totalTrimmed += count; + } + } + + } /* While (!finished) */ + + if (ruv) + ruv_destroy (&ruv); + + if (totalTrimmed) + { + slapi_log_error (SLAPI_LOG_REPL, NULL, "Trimmed %d changes from the changelog\n", totalTrimmed); + } +} + +static PRBool _cl5CanTrim (time_t time, long *numToTrim) +{ + *numToTrim = 0; + + if (s_cl5Desc.dbTrim.maxAge == 0 && s_cl5Desc.dbTrim.maxEntries == 0) + return PR_FALSE; + + if (s_cl5Desc.dbTrim.maxAge == 0) + { + *numToTrim = cl5GetOperationCount (NULL) - s_cl5Desc.dbTrim.maxEntries; + return ( *numToTrim > 0 ); + } + + if (s_cl5Desc.dbTrim.maxEntries > 0 && + (*numToTrim = cl5GetOperationCount (NULL) - s_cl5Desc.dbTrim.maxEntries) > 0) + return PR_TRUE; + + if (time) + return (current_time () - time > s_cl5Desc.dbTrim.maxAge); + else + return PR_TRUE; +} + +static int _cl5ReadRUV (const char *replGen, Object *obj, PRBool purge) +{ + int rc; + char csnStr [CSN_STRSIZE]; + DBT key={0}, data={0}; + struct berval **vals; + CL5DBFile *file; + char *pos; + char *agmt_name; + + + PR_ASSERT (replGen && obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + agmt_name = get_thread_private_agmtname(); + + if (purge) /* read purge vector entry */ + key.data = _cl5GetHelperEntryKey (PURGE_RUV_TIME, csnStr); + else /* read upper bound vector */ + key.data = _cl5GetHelperEntryKey (MAX_RUV_TIME, csnStr); + + key.size = CSN_STRSIZE; + + data.flags = DB_DBT_MALLOC; + + rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0); + switch (rc) + { + case 0: pos = data.data; + rc = _cl5ReadBervals (&vals, &pos, data.size); + free (data.data); + if (rc != CL5_SUCCESS) + return rc; + + if (purge) + rc = ruv_init_from_bervals(vals, &file->purgeRUV); + else + rc = ruv_init_from_bervals(vals, &file->maxRUV); + + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: _cl5ReadRUV: failed to initialize %s ruv; " + "RUV error %d\n", agmt_name, purge? "purge" : "upper bound", rc); + + return CL5_RUV_ERROR; + } + + ber_bvecfree(vals); + + /* delete the entry; it is re-added when file + is successfully closed */ + file->db->del (file->db, NULL, &key, DEFAULT_DB_OP_FLAGS); + + return CL5_SUCCESS; + + case DB_NOTFOUND: /* RUV is lost - need to construct */ + rc = _cl5ConstructRUV (replGen, obj, purge); + return rc; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: _cl5ReadRUV: failed to get purge RUV; " + "db error - %d %s\n", agmt_name, rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +} + +static int _cl5WriteRUV (CL5DBFile *file, PRBool purge) +{ + int rc; + DBT key={0}, data={0}; + char csnStr [CSN_STRSIZE]; + struct berval **vals; + DB_TXN *txnid = NULL; + + if ((purge && file->purgeRUV == NULL) || (!purge && file->maxRUV == NULL)) + return CL5_SUCCESS; + + if (purge) + { + key.data = _cl5GetHelperEntryKey (PURGE_RUV_TIME, csnStr); + rc = ruv_to_bervals(file->purgeRUV, &vals); + } + else + { + key.data = _cl5GetHelperEntryKey (MAX_RUV_TIME, csnStr); + rc = ruv_to_bervals(file->maxRUV, &vals); + } + + key.size = CSN_STRSIZE; + + rc = _cl5WriteBervals (vals, (char**)&data.data, &data.size); + ber_bvecfree(vals); + if (rc != CL5_SUCCESS) + { + return rc; + } + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_begin(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + rc = file->db->put(file->db, txnid, &key, &data, DEFAULT_DB_OP_FLAGS); + + slapi_ch_free ((void**)&data.data); + if ( rc == 0 ) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_commit (txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + return CL5_SUCCESS; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to write %s RUV for file %s; db error - %d\n", + purge? "purge" : "upper bound", file->name, rc); + + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + cl5_set_diskfull(); + return CL5_DB_ERROR; + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteRUV: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + return CL5_DB_ERROR; + } +} + +/* This is a very slow process since we have to read every changelog entry. + Hopefully, this function is not called too often */ +static int _cl5ConstructRUV (const char *replGen, Object *obj, PRBool purge) +{ + int rc; + CL5Entry entry; + void *iterator = NULL; + slapi_operation_parameters op = {0}; + CL5DBFile *file; + + PR_ASSERT (replGen && obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + /* construct the RUV */ + if (purge) + rc = ruv_init_new (replGen, 0, NULL, &file->purgeRUV); + else + rc = ruv_init_new (replGen, 0, NULL, &file->maxRUV); + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5ConstructRUV: " + "failed to initialize %s RUV for file %s; ruv error - %d\n", + purge? "purge" : "upper bound", file->name, rc); + return CL5_RUV_ERROR; + } + + entry.op = &op; + rc = _cl5GetFirstEntry (obj, &entry, &iterator, NULL); + while (rc == CL5_SUCCESS) + { + if (purge) + rc = ruv_set_csns_keep_smallest(file->purgeRUV, op.csn); + else + rc = ruv_set_csns (file->maxRUV, op.csn, NULL); + + cl5_operation_parameters_done (&op); + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5ConstructRUV: " + "failed to updated %s RUV for file %s; ruv error - %d\n", + purge ? "purge" : "upper bound", file->name, rc); + rc = CL5_RUV_ERROR; + continue; + } + + rc = _cl5GetNextEntry (&entry, iterator); + } + + cl5_operation_parameters_done (&op); + + if (iterator) + cl5DestroyIterator (iterator); + + if (rc == CL5_NOTFOUND) + { + rc = CL5_SUCCESS; + } + else + { + if (purge) + ruv_destroy (&file->purgeRUV); + else + ruv_destroy (&file->maxRUV); + } + + return rc; +} + +static int _cl5UpdateRUV (Object *obj, CSN *csn, PRBool newReplica, PRBool purge) +{ + ReplicaId rid; + int rc = RUV_SUCCESS; /* initialize rc to avoid erroneous logs */ + CL5DBFile *file; + + PR_ASSERT (obj && csn); + + file = (CL5DBFile*)object_get_data (obj); + + /* if purge is TRUE, file->purgeRUV must be set; + if purge is FALSE, maxRUV must be set */ + PR_ASSERT (file && ((purge && file->purgeRUV) || (!purge && file->maxRUV))); + + /* update vector only if this replica is not yet part of RUV */ + if (purge && newReplica) + { + rid = csn_get_replicaid(csn); + if (ruv_contains_replica (file->purgeRUV, rid)) + return CL5_SUCCESS; + else + { + /* if the replica is not part of the purgeRUV yet, add it */ + ruv_add_replica (file->purgeRUV, rid, multimaster_get_local_purl()); + } + } + else + { + if (purge) + rc = ruv_set_csns(file->purgeRUV, csn, NULL); + else + rc = ruv_set_csns(file->maxRUV, csn, NULL); + } + + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5UpdatePurgeRUV: " + "failed to update %s RUV for file %s; ruv error - %d\n", + purge ? "purge" : "upper bound", file->name, rc); + return CL5_RUV_ERROR; + } + + return CL5_SUCCESS; +} + +static int _cl5EnumConsumerRUV (const ruv_enum_data *element, void *arg) +{ + int rc; + RUV *ruv; + CSN *csn = NULL; + + PR_ASSERT (element && element->csn && arg); + + ruv = (RUV*)arg; + + rc = ruv_get_largest_csn_for_replica(ruv, csn_get_replicaid (element->csn), &csn); + if (rc != RUV_SUCCESS || csn == NULL || csn_compare (element->csn, csn) < 0) + { + ruv_set_max_csn(ruv, element->csn, NULL); + } + + if (csn) + csn_free (&csn); + + return 0; +} + +static int _cl5GetRUV2Purge2 (Object *fileObj, RUV **ruv) +{ + int rc = CL5_SUCCESS; + CL5DBFile *dbFile; + Object *rObj = NULL; + Replica *r = NULL; + Object *agmtObj = NULL; + Repl_Agmt *agmt; + Object *consRUVObj, *supRUVObj; + RUV *consRUV, *supRUV; + CSN *csn; + + PR_ASSERT (fileObj && ruv); + + dbFile = (CL5DBFile*)object_get_data (fileObj); + PR_ASSERT (dbFile); + + rObj = replica_get_by_name (dbFile->replName); + PR_ASSERT (rObj); + r = (Replica*)object_get_data (rObj); + PR_ASSERT (r); + + /* We start with this replica's RUV. See note in _cl5DoTrimming */ + supRUVObj = replica_get_ruv (r); + PR_ASSERT (supRUVObj); + + supRUV = (RUV*)object_get_data (supRUVObj); + PR_ASSERT (supRUV); + + *ruv = ruv_dup (supRUV); + + object_release (supRUVObj); + + agmtObj = agmtlist_get_first_agreement_for_replica (r); + while (agmtObj) + { + agmt = (Repl_Agmt*)object_get_data (agmtObj); + PR_ASSERT (agmt); + + consRUVObj = agmt_get_consumer_ruv (agmt); + if (consRUVObj) + { + consRUV = (RUV*)object_get_data (consRUVObj); + rc = ruv_enumerate_elements (consRUV, _cl5EnumConsumerRUV, *ruv); + if (rc != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5GetRUV2Purge2: " + "failed to construct ruv; ruv error - %d\n", rc); + rc = CL5_RUV_ERROR; + object_release (consRUVObj); + object_release (agmtObj); + break; + } + + object_release (consRUVObj); + } + + agmtObj = agmtlist_get_next_agreement_for_replica (r, agmtObj); + } + + /* check if there is any data in the constructed ruv - otherwise get rid of it */ + if (ruv_get_max_csn(*ruv, &csn) != RUV_SUCCESS || csn == NULL) + { + ruv_destroy (ruv); + } + else + { + csn_free (&csn); + } + + if (rObj) + object_release (rObj); + + if (rc != CL5_SUCCESS && ruv) + ruv_destroy (ruv); + + return rc; +} + +static int _cl5GetEntryCount (CL5DBFile *file) +{ + int rc; + char csnStr [CSN_STRSIZE]; + DBT key={0}, data={0}; + DB_BTREE_STAT *stats = NULL; + + PR_ASSERT (file); + + /* read entry count. if the entry is there - the file was successfully closed + last time it was used */ + key.data = _cl5GetHelperEntryKey (ENTRY_COUNT_TIME, csnStr); + key.size = CSN_STRSIZE; + + data.flags = DB_DBT_MALLOC; + + rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0); + switch (rc) + { + case 0: file->entryCount = *(int*)data.data; + free (data.data); + + /* delete the entry. the entry is re-added when file + is successfully closed */ + file->db->del (file->db, NULL, &key, DEFAULT_DB_OP_FLAGS); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetEntryCount: %d changes for replica %s\n", + file->entryCount, file->replName); + return CL5_SUCCESS; + + case DB_NOTFOUND: file->entryCount = 0; +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 3300 + rc = file->db->stat(file->db, (void*)&stats, 0); +#else + rc = file->db->stat(file->db, (void*)&stats, malloc, 0); +#endif + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetEntryCount: failed to get changelog statistics; " + "db error - %d %s\n", rc, db_strerror(rc)); + return CL5_DB_ERROR; + } + +#ifdef DB30 + file->entryCount = stats->bt_nrecs; +#else /* DB31 */ + file->entryCount = stats->bt_ndata; +#endif + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetEntryCount: %d changes for replica %s\n", + file->entryCount, file->replName); + + free (stats); + return CL5_SUCCESS; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetEntryCount: failed to get count entry; " + "db error - %d %s\n", rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +} + +static int _cl5WriteEntryCount (CL5DBFile *file) +{ + int rc; + DBT key={0}, data={0}; + char csnStr [CSN_STRSIZE]; + DB_TXN *txnid = NULL; + + key.data = _cl5GetHelperEntryKey (ENTRY_COUNT_TIME, csnStr); + key.size = CSN_STRSIZE; + data.data = (void*)&file->entryCount; + data.size = sizeof (file->entryCount); + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_begin(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + rc = file->db->put(file->db, txnid, &key, &data, DEFAULT_DB_OP_FLAGS); + if (rc == 0) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_commit (txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + return CL5_DB_ERROR; + } +#endif + return CL5_SUCCESS; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: " + "failed to write count entry for file %s; db error - %d %s\n", + file->name, rc, db_strerror(rc)); + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + cl5_set_diskfull(); + return CL5_DB_ERROR; + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteEntryCount: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + return CL5_DB_ERROR; + } +} + +static const char* _cl5OperationType2Str (int type) +{ + switch (type) + { + case SLAPI_OPERATION_ADD: return T_ADDCTSTR; + case SLAPI_OPERATION_MODIFY: return T_MODIFYCTSTR; + case SLAPI_OPERATION_MODRDN: return T_MODRDNCTSTR; + case SLAPI_OPERATION_DELETE: return T_DELETECTSTR; + default: return NULL; + } +} + +static int _cl5Str2OperationType (const char *str) +{ + if (strcasecmp (str, T_ADDCTSTR) == 0) + return SLAPI_OPERATION_ADD; + + if (strcasecmp (str, T_MODIFYCTSTR) == 0) + return SLAPI_OPERATION_MODIFY; + + if (strcasecmp (str, T_MODRDNCTSTR) == 0) + return SLAPI_OPERATION_MODRDN; + + if (strcasecmp (str, T_DELETECTSTR) == 0) + return SLAPI_OPERATION_DELETE; + + return -1; +} + +static int _cl5Operation2LDIF (const slapi_operation_parameters *op, const char *replGen, + char **ldifEntry, PRInt32 *lenLDIF) +{ + int len = 2; + lenstr *l = NULL; + const char *strType; + char *strDeleteOldRDN; + char *buff, *start; + LDAPMod **add_mods; + char *rawDN; + char strCSN[CSN_STRSIZE]; + + PR_ASSERT (op && replGen && ldifEntry && IsValidOperation (op)); + + strType = _cl5OperationType2Str (op->operation_type); + csn_as_string(op->csn,PR_FALSE,strCSN); + + /* find length of the buffer */ + len += LDIF_SIZE_NEEDED(strlen (T_CHANGETYPESTR), strlen (strType)); + len += LDIF_SIZE_NEEDED(strlen (T_REPLGEN), strlen (replGen)); + len += LDIF_SIZE_NEEDED(strlen (T_CSNSTR), strlen (strCSN)); + len += LDIF_SIZE_NEEDED(strlen (T_UNIQUEIDSTR), strlen (op->target_address.uniqueid)); + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid) + len += LDIF_SIZE_NEEDED(strlen (T_PARENTIDSTR), + strlen (op->p.p_add.parentuniqueid)); + slapi_entry2mods (op->p.p_add.target_entry, &rawDN, &add_mods); + len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (rawDN)); + l = make_changes_string(add_mods, NULL); + len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len); + ldap_mods_free (add_mods, 1); + break; + + case SLAPI_OPERATION_MODIFY: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn)); + l = make_changes_string(op->p.p_modify.modify_mods, NULL); + len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len); + break; + + case SLAPI_OPERATION_MODRDN: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn)); + len += LDIF_SIZE_NEEDED(strlen (T_NEWRDNSTR), + strlen (op->p.p_modrdn.modrdn_newrdn)); + strDeleteOldRDN = (op->p.p_modrdn.modrdn_deloldrdn ? "true" : "false"); + len += LDIF_SIZE_NEEDED(strlen (T_DRDNFLAGSTR), + strlen (strDeleteOldRDN)); + if (op->p.p_modrdn.modrdn_newsuperior_address.dn) + len += LDIF_SIZE_NEEDED(strlen (T_NEWSUPERIORDNSTR), + strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn)); + if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + len += LDIF_SIZE_NEEDED(strlen (T_NEWSUPERIORIDSTR), + strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)); + l = make_changes_string(op->p.p_modrdn.modrdn_mods, NULL); + len += LDIF_SIZE_NEEDED(strlen (T_CHANGESTR), l->ls_len); + break; + + case SLAPI_OPERATION_DELETE: len += LDIF_SIZE_NEEDED(strlen (T_DNSTR), strlen (op->target_address.dn)); + break; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Operation2LDIF: invalid operation type - %d\n", op->operation_type); + + return CL5_BAD_FORMAT; + } + + /* allocate buffer */ + buff = (char*)slapi_ch_malloc (len); + start = buff; + if (buff == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5Operation2LDIF: memory allocation failed\n"); + return CL5_MEMORY_ERROR; + } + + /* fill buffer */ + ldif_put_type_and_value(&buff, T_CHANGETYPESTR, (char*)strType, strlen (strType)); + ldif_put_type_and_value(&buff, T_REPLGEN, (char*)replGen, strlen (replGen)); + ldif_put_type_and_value(&buff, T_CSNSTR, (char*)strCSN, strlen (strCSN)); + ldif_put_type_and_value(&buff, T_UNIQUEIDSTR, op->target_address.uniqueid, + strlen (op->target_address.uniqueid)); + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.parentuniqueid) + ldif_put_type_and_value(&buff, T_PARENTIDSTR, + op->p.p_add.parentuniqueid, strlen (op->p.p_add.parentuniqueid)); + ldif_put_type_and_value(&buff, T_DNSTR, rawDN, strlen (rawDN)); + ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len); + slapi_ch_free ((void**)&rawDN); + break; + + case SLAPI_OPERATION_MODIFY: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn, + strlen (op->target_address.dn)); + ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len); + break; + + case SLAPI_OPERATION_MODRDN: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn, + strlen (op->target_address.dn)); + ldif_put_type_and_value(&buff, T_NEWRDNSTR, op->p.p_modrdn.modrdn_newrdn, + strlen (op->p.p_modrdn.modrdn_newrdn)); + ldif_put_type_and_value(&buff, T_DRDNFLAGSTR, strDeleteOldRDN, + strlen (strDeleteOldRDN)); + if (op->p.p_modrdn.modrdn_newsuperior_address.dn) + ldif_put_type_and_value(&buff, T_NEWSUPERIORDNSTR, + op->p.p_modrdn.modrdn_newsuperior_address.dn, + strlen (op->p.p_modrdn.modrdn_newsuperior_address.dn)); + if (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid) + ldif_put_type_and_value(&buff, T_NEWSUPERIORIDSTR, + op->p.p_modrdn.modrdn_newsuperior_address.uniqueid, + strlen (op->p.p_modrdn.modrdn_newsuperior_address.uniqueid)); + ldif_put_type_and_value(&buff, T_CHANGESTR, l->ls_buf, l->ls_len); + break; + + case SLAPI_OPERATION_DELETE: ldif_put_type_and_value(&buff, T_DNSTR, op->target_address.dn, + strlen (op->target_address.dn)); + break; + } + + *buff = '\n'; + buff ++; + *buff = '\0'; + + *ldifEntry = start; + *lenLDIF = buff - start; + + if (l) + lenstr_free(&l); + + return CL5_SUCCESS; +} + +static int +_cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, char **replGen) +{ + int rc; + int vlen; + char *next, *line; + char *type, *value; + Slapi_Mods *mods; + char *rawDN; + + PR_ASSERT (op && ldifEntry && replGen); + + memset (op, 0, sizeof (*op)); + + next = ldifEntry; + while ((line = ldif_getline(&next)) != NULL) + { + char *errmsg = NULL; + + if ( *line == '\n' || *line == '\0' ) + { + break; + } + + /* this call modifies ldifEntry */ + rc = ldif_parse_line(line, &type, &value, &vlen, &errmsg); + if (rc != 0) + { + if ( errmsg != NULL ) { + slapi_log_error(SLAPI_LOG_PARSE, repl_plugin_name_cl, "%s", errmsg); + slapi_ch_free( (void**)&errmsg ); + } + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5LDIF2Operation: warning - failed to parse ldif line\n"); + continue; + } + + if (strcasecmp (type, T_CHANGETYPESTR) == 0) + { + op->operation_type = _cl5Str2OperationType (value); + } + else if (strcasecmp (type, T_REPLGEN) == 0) + { + *replGen = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_CSNSTR) == 0) + { + op->csn = csn_new_by_string(value); + } + else if (strcasecmp (type, T_UNIQUEIDSTR) == 0) + { + op->target_address.uniqueid = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_DNSTR) == 0) + { + PR_ASSERT (op->operation_type); + + if (op->operation_type == SLAPI_OPERATION_ADD) + { + rawDN = slapi_ch_strdup (value); + op->target_address.dn = slapi_ch_strdup(rawDN); + } + else + op->target_address.dn = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_PARENTIDSTR) == 0) + { + op->p.p_add.parentuniqueid = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_NEWRDNSTR) == 0) + { + op->p.p_modrdn.modrdn_newrdn = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_DRDNFLAGSTR) == 0) + { + op->p.p_modrdn.modrdn_deloldrdn = (strcasecmp (value, "true") ? PR_FALSE : PR_TRUE); + } + else if (strcasecmp (type, T_NEWSUPERIORDNSTR) == 0) + { + op->p.p_modrdn.modrdn_newsuperior_address.dn = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_NEWSUPERIORIDSTR) == 0) + { + op->p.p_modrdn.modrdn_newsuperior_address.uniqueid = slapi_ch_strdup (value); + } + else if (strcasecmp (type, T_CHANGESTR) == 0) + { + PR_ASSERT (op->operation_type); + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: mods = parse_changes_string(value); + slapi_mods2entry (&(op->p.p_add.target_entry), rawDN, + slapi_mods_get_ldapmods_byref(mods)); + slapi_ch_free ((void**)&rawDN); + slapi_mods_free (&mods); + break; + + case SLAPI_OPERATION_MODIFY: mods = parse_changes_string(value); + PR_ASSERT (mods); + op->p.p_modify.modify_mods = slapi_mods_get_ldapmods_passout (mods); + slapi_mods_free (&mods); + break; + + case SLAPI_OPERATION_MODRDN: mods = parse_changes_string(value); + PR_ASSERT (mods); + op->p.p_modrdn.modrdn_mods = slapi_mods_get_ldapmods_passout (mods); + slapi_mods_free (&mods); + break; + + default: slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5LDIF2Operation: invalid operation type - %d\n", + op->operation_type); + return CL5_BAD_FORMAT; + } + } + } + + if (IsValidOperation (op)) + return CL5_SUCCESS; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5LDIF2Operation: invalid data format\n"); + return CL5_BAD_FORMAT; +} + +static int _cl5WriteOperation(const char *replName, const char *replGen, + const slapi_operation_parameters *op, PRBool local) +{ + int rc; + int cnt; + DBT key={0}; + DBT * data=NULL; + char csnStr [CSN_STRSIZE]; + PRIntervalTime interval; + CL5Entry entry; + CL5DBFile *file = NULL; + Object *file_obj = NULL; + DB_TXN *txnid = NULL; + + rc = _cl5GetDBFileByReplicaName (replName, replGen, &file_obj); + if (rc == CL5_NOTFOUND) + { + rc = _cl5DBOpenFileByReplicaName (replName, replGen, &file_obj, + PR_TRUE /* check for duplicates */); + if (rc != CL5_SUCCESS) + { + return rc; + } + } + else if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "_cl5WriteOperation: failed to get db file for target dn (%s)", + op->target_address.dn); + return CL5_OBJSET_ERROR; + } + + /* assign entry time - used for trimming */ + entry.time = current_time (); + entry.op = (slapi_operation_parameters *)op; + + /* construct the key */ + key.data = csn_as_string(op->csn, PR_FALSE, csnStr); + key.size = CSN_STRSIZE; + + /* construct the data */ + data = (DBT *) slapi_ch_calloc(1, sizeof(DBT)); + rc = _cl5Entry2DBData (&entry, (char**)&data->data, &data->size); + if (rc != CL5_SUCCESS) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to convert entry with csn (%s) " + "to db format\n", csn_as_string(op->csn,PR_FALSE,s)); + goto done; + } + + file = (CL5DBFile*)object_get_data (file_obj); + PR_ASSERT (file); + + /* if this is part of ldif2cl - just write the entry without transaction */ + if (s_cl5Desc.dbOpenMode == CL5_OPEN_LDIF2CL) + { + rc = file->db->put(file->db, NULL, &key, data, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to write entry; db error - %d %s\n", + rc, db_strerror(rc)); + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + cl5_set_diskfull(); + } + rc = CL5_DB_ERROR; + } + goto done; + } + + /* write the entry */ + rc = EAGAIN; + cnt = 0; + + while ((rc == EAGAIN || rc == DB_LOCK_DEADLOCK) && cnt < MAX_TRIALS) + { + if (cnt != 0) + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + /* abort previous transaction */ + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } +#endif + /* back off */ + interval = PR_MillisecondsToInterval(slapi_rand() % 100); + DS_Sleep(interval); + } +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + /* begin transaction */ + rc = txn_begin(s_cl5Desc.dbEnv, NULL /*pid*/, &txnid, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to start transaction; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } +#endif + + if ( file->sema ) + { + PR_WaitSemaphore(file->sema); + } + rc = file->db->put(file->db, txnid, &key, data, DEFAULT_DB_OP_FLAGS); + if ( file->sema ) + { + PR_PostSemaphore(file->sema); + } + if (CL5_OS_ERR_IS_DISKFULL(rc)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: changelog (%s) DISK FULL; db error - %d %s\n", + s_cl5Desc.dbDir, rc, db_strerror(rc)); + cl5_set_diskfull(); + rc = CL5_DB_ERROR; + goto done; + } + if (cnt != 0) + { + if (rc == 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) succeeded\n", cnt, (char*)key.data); + } + else if ((cnt + 1) >= MAX_TRIALS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) failed (rc=%d)\n", cnt, (char*)key.data, rc); + } + } + cnt ++; + } + + if (rc == 0) /* we successfully added entry */ + { +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_commit (txnid, 0); +#endif + } + else + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to write entry with csn (%s); " + "db error - %d %s\n", csn_as_string(op->csn,PR_FALSE,s), + rc, db_strerror(rc)); +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100 + rc = txn_abort (txnid); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5WriteOperation: failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } +#endif + rc = CL5_DB_ERROR; + goto done; + } + + /* update entry count - we assume that all entries are new */ + PR_AtomicIncrement (&file->entryCount); + + /* update purge vector if we have not seen any changes from this replica before */ + _cl5UpdateRUV (file_obj, op->csn, PR_TRUE, PR_TRUE); + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "cl5WriteOperation: successfully written entry with csn (%s)\n", csnStr); + rc = CL5_SUCCESS; +done: + if (data->data) + slapi_ch_free ((void**)&data->data); + slapi_ch_free((void**) &data); + + if (file_obj) + object_release (file_obj); + + return rc; +} + +static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid) +{ + int rc; + DBC *cursor = NULL; + DBT key={0}, data={0}; + CL5Iterator *it; + CL5DBFile *file; + + PR_ASSERT (obj && entry && iterator); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + /* create cursor */ + rc = file->db->cursor(file->db, txnid, &cursor, 0); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetFirstEntry: failed to create cursor; db error - %d %s\n", rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + key.flags = DB_DBT_MALLOC; + data.flags = DB_DBT_MALLOC; + while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) + { + /* skip service entries */ + if (cl5HelperEntry ((char*)key.data, NULL)) + { + free (key.data); + free (data.data); + continue; + } + + /* format entry */ + free (key.data); + rc = cl5DBData2Entry (data.data, data.size, entry); + free (data.data); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetFirstOperation: failed to format entry\n", rc); + goto done; + } + + it = (CL5Iterator*)slapi_ch_malloc (sizeof (CL5Iterator)); + it->cursor = cursor; + object_acquire (obj); + it->file = obj; + *(CL5Iterator**)iterator = it; + + return CL5_SUCCESS; + } + + /* walked of the end of the file */ + if (rc == DB_NOTFOUND) + { + rc = CL5_NOTFOUND; + goto done; + } + + /* db error occured while iterating */ + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetFirstEntry: failed to get entry; db error - %d %s\n", rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + /* successfully retrieved next entry but it was out of range */ + if (rc == CL5_SUCCESS) + { + free (key.data); + free (data.data); + rc = CL5_NOTFOUND; + goto done; + } + +done:; + /* error occured */ + /* We didn't success in assigning this cursor to the iterator, + * so we need to free the cursor here */ + if (cursor) + cursor->c_close(cursor); + + return rc; +} + +static int _cl5GetNextEntry (CL5Entry *entry, void *iterator) +{ + int rc; + CL5Iterator *it; + DBT key={0}, data={0}; + + PR_ASSERT (entry && iterator); + + it = (CL5Iterator*) iterator; + + key.flags = DB_DBT_MALLOC; + data.flags = DB_DBT_MALLOC; + while ((rc = it->cursor->c_get(it->cursor, &key, &data, DB_NEXT)) == 0) + { + if (cl5HelperEntry ((char*)key.data, NULL)) + { + free (key.data); + free (data.data); + continue; + } + + free (key.data); + /* format entry */ + rc = cl5DBData2Entry (data.data, data.size, entry); + free (data.data); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetNextEntry: failed to format entry\n", rc); + } + + return rc; + } + + /* walked of the end of the file or entry is out of range */ + if (rc == 0 || rc == DB_NOTFOUND) + { + return CL5_NOTFOUND; + } + + /* cursor operation failed */ + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5GetNextEntry: failed to get entry; db error - %d %s\n", rc, db_strerror(rc)); + + return CL5_DB_ERROR; + } + + return rc; +} + +static int _cl5CurrentDeleteEntry (void *iterator) +{ + int rc; + CL5Iterator *it; + CL5DBFile *file; + + PR_ASSERT (iterator); + + it = (CL5Iterator*)iterator; + + rc = it->cursor->c_del (it->cursor, 0); + + if (rc == 0) { + /* decrement entry count */ + file = (CL5DBFile*)object_get_data (it->file); + PR_AtomicDecrement (&file->entryCount); + return CL5_SUCCESS; + } else { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CurrentDeleteEntry failed, err=%d %s\n", + rc, db_strerror(rc)); + /* We don't free(close) the cursor here, as the caller will free it by a call to cl5DestroyIterator */ + /* Freeing it here is a potential bug, as the cursor can't be referenced later once freed */ + return CL5_DB_ERROR; + } +} + +static PRBool _cl5IsValidIterator (const CL5Iterator *iterator) +{ + return (iterator && iterator->cursor && iterator->file); +} + +static int _cl5GetOperation (Object *replica, slapi_operation_parameters *op) +{ + int rc; + DBT key={0}, data={0}; + CL5DBFile *file; + CL5Entry entry; + Object *obj = NULL; + char csnStr[CSN_STRSIZE]; + + rc = _cl5GetDBFile (replica, &obj); + if (rc != CL5_SUCCESS) + { + return rc; + } + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + /* construct the key */ + key.data = csn_as_string(op->csn, PR_FALSE, csnStr); + key.size = CSN_STRSIZE; + + data.flags = DB_DBT_MALLOC; + + rc = file->db->get(file->db, NULL/*txn*/, &key, &data, 0); + switch (rc) + { + case 0: entry.op = op; + /* Callers of this function should cl5_operation_parameters_done(op) */ + rc = cl5DBData2Entry (data.data, data.size, &entry); + if (rc == CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "_cl5GetOperation: successfully retrieved operation with csn (%s)\n", + csnStr); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetOperation: failed to convert db data to operation;" + " csn - %s\n", csnStr); + } + goto done; + + case DB_NOTFOUND: slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetOperation: operation for csn (%s) is not found in db that should contain dn (%s)\n", + csnStr, op->target_address.dn); + rc = CL5_NOTFOUND; + goto done; + + default: slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5GetOperation: failed to get entry for csn (%s); " + "db error - %d %s\n", csnStr, rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + +done:; + if (obj) + object_release (obj); + + if (data.data) + free (data.data); + + return rc; +} + +PRBool cl5HelperEntry (const char *csnstr, CSN *csnp) +{ + CSN *csn; + time_t csnTime; + PRBool retval = PR_FALSE; + + if (csnp) + { + csn = csnp; + } + else + { + csn= csn_new_by_string(csnstr); + } + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "cl5HelperEntry: failed to get csn time; csn error\n"); + return PR_FALSE; + } + csnTime= csn_get_time(csn); + + if (csnTime == ENTRY_COUNT_TIME || csnTime == PURGE_RUV_TIME) + { + retval = PR_TRUE; + } + + if (NULL == csnp) + csn_free(&csn); + return retval; +} + +/* Replay iteration helper functions */ +static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator) +{ + if (iterator == NULL || + iterator->consumerRuv == NULL || iterator->supplierRuvObj == NULL || + iterator->fileObj == NULL) + return PR_FALSE; + + return PR_TRUE; +} + +/* Algorithm: ONREPL!!! + */ +struct replica_hash_entry +{ + ReplicaId rid; /* replica id */ + PRBool sendChanges; /* indicates whether changes should be sent for this replica */ +}; + + +static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv, + Object *replica, Object *fileObj, CL5ReplayIterator **iterator) +{ + CLC_Buffer *clcache = NULL; + CL5DBFile *file; + int i; + CSN **csns = NULL; + CSN *startCSN = NULL; + char csnStr [CSN_STRSIZE]; + int rc = CL5_SUCCESS; + Object *supplierRuvObj = NULL; + RUV *supplierRuv = NULL; + ReplicaId supplierRID; + PRBool newReplica; + PRBool haveChanges = PR_FALSE; + char *agmt_name; + ReplicaId rid; + + PR_ASSERT (consumerRuv && replica && fileObj && iterator); + csnStr[0] = '\0'; + + file = (CL5DBFile*)object_get_data (fileObj); + supplierRID = replica_get_rid((Replica*)object_get_data(replica)); + + /* get supplier's RUV */ + supplierRuvObj = replica_get_ruv((Replica*)object_get_data(replica)); + PR_ASSERT (supplierRuvObj); + supplierRuv = (RUV*)object_get_data (supplierRuvObj); + PR_ASSERT (supplierRuv); + + agmt_name = get_thread_private_agmtname(); + slapi_log_error(SLAPI_LOG_REPL, NULL, "_cl5PositionCursorForReplay (%s): Consumer RUV:\n", agmt_name); + ruv_dump (consumerRuv, agmt_name, NULL); + slapi_log_error(SLAPI_LOG_REPL, NULL, "_cl5PositionCursorForReplay (%s): Supplier RUV:\n", agmt_name); + ruv_dump (supplierRuv, agmt_name, NULL); + + /* + * get the sorted list of SupplierMinCSN (if no ConsumerMaxCSN) + * and ConsumerMaxCSN for those RIDs where consumer is not + * up-to-date. + */ + csns = cl5BuildCSNList (consumerRuv, supplierRuv); + if (csns == NULL) + { + rc = CL5_NOTFOUND; + goto done; + } + + /* iterate over elements of consumer's (and/or supplier's) ruv */ + for (i = 0; csns[i]; i++) + { + CSN *consumerMaxCSN = NULL; + + rid = csn_get_replicaid(csns[i]); + + /* + * Skip CSN that is originated from the consumer. + * If RID==65535, the CSN is originated from a + * legacy consumer. In this case the supplier + * and the consumer may have the same RID. + */ + if (rid == consumerRID && rid != MAX_REPLICA_ID) + continue; + + startCSN = csns[i]; + csn_as_string(startCSN, PR_FALSE, csnStr); + + rc = clcache_get_buffer ( &clcache, file->db, consumerRID, consumerRuv, supplierRuv ); + if ( rc != 0 ) goto done; + + /* This is the first loading of this iteration. For replicas + * already known to the consumer, we exclude the last entry + * sent to the consumer by using DB_NEXT. However, for + * replicas new to the consumer, we include the first change + * ever generated by that replica. + */ + newReplica = ruv_get_largest_csn_for_replica (consumerRuv, rid, &consumerMaxCSN); + csn_free(&consumerMaxCSN); + rc = clcache_load_buffer (clcache, startCSN, (newReplica ? DB_SET : DB_NEXT)); + + /* there is a special case which can occur just after migration - in this case, + the consumer RUV will contain the last state of the supplier before migration, + but the supplier will have an empty changelog, or the supplier changelog will + not contain any entries within the consumer min and max CSN - also, since + the purge RUV contains no CSNs, the changelog has never been purged + ASSUMPTIONS - it is assumed that the supplier had no pending changes to send + to any consumers; that is, we can assume that no changes were lost due to + either changelog purging or database reload - bug# 603061 - richm@netscape.com + */ + if (rc == 0 || (rc == DB_NOTFOUND && !ruv_has_csns(file->purgeRUV))) + { + haveChanges = PR_TRUE; + rc = CL5_SUCCESS; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: CSN %s found, position set for replay\n", agmt_name, csnStr); + break; + } + else if (rc == DB_NOTFOUND) /* entry not found */ + { + /* check whether this csn should be present */ + rc = _cl5CheckMissingCSN (startCSN, supplierRuv, file); + if (rc == CL5_MISSING_DATA) /* we should have had the change but we don't */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "%s: CSN %s not found, seems to be missing\n", agmt_name, csnStr); + break; + } + else /* we are not as up to date or we purged */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: CSN %s not found, we aren't as up to date, or we purged\n", + agmt_name, csnStr); + continue; + } + } + else + { + + /* db error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: Failed to retrieve change with CSN %s; db error - %d %s\n", + agmt_name, csnStr, rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + break; + } + + } /* end for */ + + /* setup the iterator */ + if (haveChanges) + { + *iterator = (CL5ReplayIterator*) slapi_ch_calloc (1, sizeof (CL5ReplayIterator)); + + if (*iterator == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "%s: _cl5PositionCursorForReplay: failed to allocate iterator\n", agmt_name); + rc = CL5_MEMORY_ERROR; + goto done; + } + + /* ONREPL - should we make a copy of both RUVs here ?*/ + (*iterator)->fileObj = fileObj; + (*iterator)->clcache = clcache; clcache = NULL; + (*iterator)->consumerRID = consumerRID; + (*iterator)->consumerRuv = consumerRuv; + (*iterator)->supplierRuvObj = supplierRuvObj; + } + else if (rc == CL5_SUCCESS) + { + /* we have no changes to send */ + rc = CL5_NOTFOUND; + } + +done: + if ( clcache ) + clcache_return_buffer ( &clcache ); + + if (csns) + cl5DestroyCSNList (&csns); + + if (rc != CL5_SUCCESS) + { + if (supplierRuvObj) + object_release (supplierRuvObj); + } + + return rc; +} + +struct ruv_it +{ + CSN **csns; /* csn list */ + int alloc; /* allocated size */ + int pos; /* position in the list */ +}; + +static int ruv_consumer_iterator (const ruv_enum_data *enum_data, void *arg) +{ + struct ruv_it *data = (struct ruv_it*)arg; + + PR_ASSERT (data); + + /* check if we have space for one more element */ + if (data->pos >= data->alloc - 2) + { + data->alloc += 4; + data->csns = (CSN**) slapi_ch_realloc ((void*)data->csns, data->alloc * sizeof (CSN*)); + } + + data->csns [data->pos] = csn_dup (enum_data->csn); + data->pos ++; + + return 0; +} + + +static int ruv_supplier_iterator (const ruv_enum_data *enum_data, void *arg) +{ + int i; + PRBool found = PR_FALSE; + ReplicaId rid; + struct ruv_it *data = (struct ruv_it*)arg; + + PR_ASSERT (data); + + rid = csn_get_replicaid (enum_data->min_csn); + /* check if the replica that generated the csn is already in the list */ + for (i = 0; i < data->pos; i++) + { + if (rid == csn_get_replicaid (data->csns[i])) + { + found = PR_TRUE; + + /* remove datacsn[i] if it is greater or equal to the supplier's maxcsn */ + if ( csn_compare ( data->csns[i], enum_data->csn ) >= 0 ) + { + int j; + + csn_free ( & data->csns[i] ); + for (j = i+1; j < data->pos; j++) + { + data->csns [j-1] = data->csns [j]; + } + data->pos --; + } + break; + } + } + + if (!found) + { + /* check if we have space for one more element */ + if (data->pos >= data->alloc - 2) + { + data->alloc += 4; + data->csns = (CSN**)slapi_ch_realloc ((void*)data->csns, + data->alloc * sizeof (CSN*)); + } + + data->csns [data->pos] = csn_dup (enum_data->min_csn); + data->pos ++; + } + return 0; +} + + + +static int +my_csn_compare(const void *arg1, const void *arg2) +{ + return(csn_compare(*((CSN **)arg1), *((CSN **)arg2))); +} + + + +/* builds CSN ordered list of all csns in the RUV */ +CSN** cl5BuildCSNList (const RUV *consRuv, const RUV *supRuv) +{ + struct ruv_it data; + int count, rc; + CSN **csns; + + PR_ASSERT (consRuv); + + count = ruv_replica_count (consRuv); + csns = (CSN**)slapi_ch_calloc (count + 1, sizeof (CSN*)); + + data.csns = csns; + data.alloc = count + 1; + data.pos = 0; + + /* add consumer elements to the list */ + rc = ruv_enumerate_elements (consRuv, ruv_consumer_iterator, &data); + if (rc == 0 && supRuv) + { + /* add supplier elements to the list */ + rc = ruv_enumerate_elements (supRuv, ruv_supplier_iterator, &data); + } + + /* we have no csns */ + if (data.csns[0] == NULL) + { + /* csns might have been realloced in ruv_supplier_iterator() */ + slapi_ch_free ((void**)&data.csns); + csns = NULL; + } + else + { + csns = data.csns; + data.csns [data.pos] = NULL; + if (rc == 0) + { + qsort (csns, data.pos, sizeof (CSN*), my_csn_compare); + } + else + { + cl5DestroyCSNList (&csns); + } + } + + return csns; +} + +void cl5DestroyCSNList (CSN*** csns) +{ + if (csns && *csns) + { + int i; + + for (i = 0; (*csns)[i]; i++) + { + csn_free (&(*csns)[i]); + } + + slapi_ch_free ((void**)csns); + } +} + +/* A csn should be in the changelog if it is larger than purge vector csn for the same + replica and is smaller than the csn in supplier's ruv for the same replica. + The functions returns + CL5_PURGED if data was purged from the changelog or was never logged + because it was loaded as part of replica initialization + CL5_MISSING if the data erouneously missing + CL5_SUCCESS if that has not and should not been seen by the server + */ +static int _cl5CheckMissingCSN (const CSN *csn, const RUV *supplierRuv, CL5DBFile *file) +{ + ReplicaId rid; + CSN *supplierCsn = NULL; + CSN *purgeCsn = NULL; + int rc = CL5_SUCCESS; + char csnStr [CSN_STRSIZE]; + + PR_ASSERT (csn && supplierRuv && file); + + rid = csn_get_replicaid (csn); + ruv_get_largest_csn_for_replica (supplierRuv, rid, &supplierCsn); + if (supplierCsn == NULL) + { + /* we have not seen any changes from this replica so it is + ok not to have this csn */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "can't locate %s csn: we have not seen any changes for replica %d\n", + csn_as_string (csn, PR_FALSE, csnStr), rid); + return CL5_SUCCESS; + } + + ruv_get_largest_csn_for_replica (file->purgeRUV, rid, &purgeCsn); + if (purgeCsn == NULL) + { + /* changelog never contained any changes for this replica */ + if (csn_compare (csn, supplierCsn) <= 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "the change with %s csn was never logged because it was imported " + "during replica initialization\n", csn_as_string (csn, PR_FALSE, csnStr)); + rc = CL5_PURGED_DATA; /* XXXggood is that the correct return value? */ + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "change with %s csn has not yet been seen by this server; " + " last csn seen from that replica is %s\n", + csn_as_string (csn, PR_FALSE, csnStr), + csn_as_string (supplierCsn, PR_FALSE, csnStr)); + rc = CL5_SUCCESS; + } + } + else /* we have both purge and supplier csn */ + { + if (csn_compare (csn, purgeCsn) < 0) /* the csn is below the purge point */ + { + rc = CL5_PURGED_DATA; + } + else + { + if (csn_compare (csn, supplierCsn) <= 0) /* we should have the data but we don't */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "change with %s csn has been purged by this server; " + "the current purge point for that replica is %s\n", + csn_as_string (csn, PR_FALSE, csnStr), + csn_as_string (purgeCsn, PR_FALSE, csnStr)); + rc = CL5_MISSING_DATA; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5CheckMissingCSN: " + "change with %s csn has not yet been seen by this server; " + " last csn seen from that replica is %s\n", + csn_as_string (csn, PR_FALSE, csnStr), + csn_as_string (supplierCsn, PR_FALSE, csnStr)); + rc = CL5_SUCCESS; + } + } + } + + if (supplierCsn) + csn_free (&supplierCsn); + + if (purgeCsn) + csn_free (&purgeCsn); + + return rc; +} + +/* Helper functions that work with individual changelog files */ + +/* file name format : <replica name>_<replica generation>db{2,3} */ +static PRBool _cl5FileName2Replica (const char *file_name, Object **replica) +{ + Replica *r; + char *repl_name, *file_gen, *repl_gen; + int len; + + PR_ASSERT (file_name && replica); + + *replica = NULL; + + /* this is database file */ + if (_cl5FileEndsWith (file_name, DB_EXTENSION) || + _cl5FileEndsWith (file_name, DB_EXTENSION_DB3) ) + { + repl_name = slapi_ch_strdup (file_name); + file_gen = strstr(repl_name, FILE_SEP); + if (file_gen) + { + int extlen = strlen(DB_EXTENSION); + *file_gen = '\0'; + file_gen += strlen (FILE_SEP); + len = strlen (file_gen); + if (len <= extlen + 1) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica " + "invalid file name (%s)\n", file_name); + } + else + { + /* get rid of the file extension */ + file_gen [len - extlen - 1] = '\0'; + *replica = replica_get_by_name (repl_name); + if (*replica) + { + /* check that generation matches the one in replica object */ + r = (Replica*)object_get_data (*replica); + repl_gen = replica_get_generation (r); + PR_ASSERT (repl_gen); + if (strcmp (file_gen, repl_gen) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica " + "replica generation mismatch for replica at (%s), " + "file generation %s, new replica generation %s\n", + slapi_sdn_get_dn (replica_get_root (r)), file_gen, repl_gen); + + object_release (*replica); + *replica = NULL; + } + slapi_ch_free ((void**)&repl_gen); + } + } + slapi_ch_free ((void**)&repl_name); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5FileName2Replica " + "malformed file name - %s\n", file_name); + } + + return PR_TRUE; + } + else + return PR_FALSE; +} + +/* file name format : <replica name>_<replica generation>db{2,3} */ +static char* _cl5Replica2FileName (Object *replica) +{ + const char *replName; + char *replGen, *fileName; + Replica *r; + + PR_ASSERT (replica); + + r = (Replica*)object_get_data (replica); + PR_ASSERT (r); + + replName = replica_get_name (r); + replGen = replica_get_generation (r); + + fileName = _cl5MakeFileName (replName, replGen) ; + + slapi_ch_free ((void**)&replGen); + + return fileName; +} + +static char* _cl5MakeFileName (const char *replName, const char *replGen) +{ + char *fileName; + fileName = slapi_ch_malloc (strlen (replName) + strlen (replGen) + + strlen (DB_EXTENSION) + 3/* '_' + '.' + '\0' */); + sprintf (fileName, "%s%s%s.%s", replName, FILE_SEP, replGen, DB_EXTENSION); + + return fileName; +} + +/* open file that corresponds to a particular database */ +static int _cl5DBOpenFile (Object *replica, Object **obj, PRBool checkDups) +{ + int rc; + const char *replName; + char *replGen; + Replica *r; + + PR_ASSERT (replica); + + r = (Replica*)object_get_data (replica); + replName = replica_get_name (r); + PR_ASSERT (replName); + replGen = replica_get_generation (r); + PR_ASSERT (replGen); + + rc = _cl5DBOpenFileByReplicaName (replName, replGen, obj, checkDups); + + slapi_ch_free ((void**)&replGen); + + return rc; +} + +static int _cl5DBOpenFileByReplicaName (const char *replName, const char *replGen, + Object **obj, PRBool checkDups) +{ + int rc = CL5_SUCCESS; + Object *tmpObj; + CL5DBFile *file; + char *file_name; + + PR_ASSERT (replName && replGen); + + if (checkDups) + { + PR_Lock (s_cl5Desc.fileLock); + file_name = _cl5MakeFileName (replName, replGen); + tmpObj = objset_find (s_cl5Desc.dbFiles, _cl5CompareDBFile, file_name); + slapi_ch_free((void **)&file_name); + file_name = NULL; + if (tmpObj) /* this file already exist */ + { + /* if we were asked for file handle - keep the handle */ + if (obj) + { + *obj = tmpObj; + } + else + { + object_release (tmpObj); + } + + rc = CL5_SUCCESS; + goto done; + } + } + + rc = _cl5NewDBFile (replName, replGen, &file); + if (rc == CL5_SUCCESS) + { + /* This creates the file but doesn't set the init flag + * The flag is set later when the purge and max ruvs are set. + * This is to prevent some thread to get file access before the + * structure is fully initialized */ + rc = _cl5AddDBFile (file, &tmpObj); + if (rc == CL5_SUCCESS) + { + /* read purge RUV - done here because it needs file object rather than file pointer */ + rc = _cl5ReadRUV (replGen, tmpObj, PR_TRUE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DBOpenFileByReplicaName: failed to get purge RUV\n"); + goto done; + } + + /* read ruv that represents the upper bound of the changes stored in the file */ + rc = _cl5ReadRUV (replGen, tmpObj, PR_FALSE); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5DBOpenFileByReplicaName: failed to get upper bound RUV\n"); + goto done; + } + + /* Mark the DB File initialize */ + _cl5DBFileInitialized(tmpObj); + + if (obj) + { + *obj = tmpObj; + } + else + { + object_release (tmpObj); + } + } + } + +done:; + if (rc != CL5_SUCCESS) + { + if (file) + _cl5DBCloseFile ((void**)&file); + } + + if (checkDups) + { + PR_Unlock (s_cl5Desc.fileLock); + } + + return rc; +} + +/* adds file to the db file list */ +static int _cl5AddDBFile (CL5DBFile *file, Object **obj) +{ + int rc; + Object *tmpObj; + + PR_ASSERT (file); + + tmpObj = object_new (file, _cl5DBCloseFile); + rc = objset_add_obj(s_cl5Desc.dbFiles, tmpObj); + if (rc != OBJSET_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5AddDBFile: failed to add db file to the list; " + "repl_objset error - %d\n", rc); + object_release (tmpObj); + return CL5_OBJSET_ERROR; + } + + if (obj) + { + *obj = tmpObj; + } + else + object_release (tmpObj); + + return CL5_SUCCESS; +} + +static int _cl5NewDBFile (const char *replName, const char *replGen, CL5DBFile** dbFile) +{ + int rc; + DB *db = NULL; + char *name; + char *semadir; +#ifdef HPUX + char cwd [PATH_MAX+1]; +#endif + + PR_ASSERT (replName && replGen && dbFile); + + (*dbFile) = (CL5DBFile *)slapi_ch_calloc (1, sizeof (CL5DBFile)); + if (*dbFile == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5NewDBFile: memory allocation failed\n"); + return CL5_MEMORY_ERROR; + } + + name = _cl5MakeFileName (replName, replGen); + { + /* The subname argument allows applications to have + * subdatabases, i.e., multiple databases inside of a single + * physical file. This is useful when the logical databases + * are both numerous and reasonably small, in order to + * avoid creating a large number of underlying files. + */ + char *subname = NULL; + DB_ENV *dbEnv = s_cl5Desc.dbEnv; + + rc = db_create(&db, dbEnv, 0); + if (0 != rc) { + goto out; + } + + rc = db->set_pagesize( + db, + s_cl5Desc.dbConfig.pageSize); + + if (0 != rc) { + goto out; + } + +#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 3300 + rc = db->set_malloc(db, malloc); + if (0 != rc) { + goto out; + } +#endif + + DB_OPEN(s_cl5Desc.dbEnvOpenFlags, + db, NULL /* txnid */, name, subname, DB_BTREE, + DB_CREATE | DB_THREAD, s_cl5Desc.dbConfig.fileMode, rc); + } +out: + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5NewDBFile: db_open failed; db error - %d %s\n", + rc, db_strerror(rc)); + rc = CL5_DB_ERROR; + goto done; + } + + (*dbFile)->db = db; + (*dbFile)->name = name; + (*dbFile)->replName = slapi_ch_strdup (replName); + (*dbFile)->replGen = slapi_ch_strdup (replGen); + + /* + * Considerations for setting up cl semaphore: + * (1) The NT version of SleepyCat uses test-and-set mutexes + * at the DB page level instead of blocking mutexes. That has + * proven to be a killer for the changelog DB, as this DB is + * accessed by multiple a reader threads (the repl thread) and + * writer threads (the server ops threads) usually at the last + * pages of the DB, due to the sequential nature of the changelog + * keys. To avoid the test-and-set mutexes, we could use semaphore + * to serialize the writers and avoid the high mutex contention + * that SleepyCat is unable to avoid. + * (2) [610948] Linux master hangs for 2 hours + * [611239] _cl5DeadlockMain: lock_detect succeeded + * (3) DS 6.2 introduced the semaphore on all platforms (replaced + * the serial lock used on Windows and Linux described above). + * The number of the concurrent writes now is configurable by + * nsslapd-changelogmaxconcurrentwrites (the server needs to + * be restarted). + */ + + semadir = s_cl5Desc.dbDir; +#ifdef HPUX + /* + * HP sem_open() does not allow pathname component "./" or "../" + * in the semaphore name. For simplicity and to avoid doing + * chdir() in multi-thread environment, current working dir + * (log dir) is used to replace the original semaphore dir + * if it contains "./". + */ + if ( strstr ( semadir, "./" ) != NULL && getcwd ( cwd, PATH_MAX+1 ) != NULL ) + { + semadir = cwd; + } +#endif + + if ( semadir != NULL ) + { + (*dbFile)->semaName = slapi_ch_malloc (strlen(semadir) + strlen(replName) + strlen(".sema") + 10); + sprintf ((*dbFile)->semaName, "%s/%s.sema", semadir, replName); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5NewDBFile: semaphore %s\n", (*dbFile)->semaName); + (*dbFile)->sema = PR_OpenSemaphore((*dbFile)->semaName, PR_SEM_CREATE, 0666, s_cl5Desc.dbConfig.maxConcurrentWrites ); + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5NewDBFile: maxConcurrentWrites=%d\n", s_cl5Desc.dbConfig.maxConcurrentWrites ); + } + + if ((*dbFile)->sema == NULL ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5NewDBFile: failed to create semaphore %s; NSPR error - %d\n", + (*dbFile)->semaName ? (*dbFile)->semaName : "(nil)", PR_GetError ()); + rc = CL5_SYSTEM_ERROR; + goto done; + } + + /* compute number of entries in the file */ + /* ONREPL - to improve performance, we keep entry count in memory + and write it down during shutdown. Problem: this will not + work with multiple processes. Do we have to worry about that? + */ + if (s_cl5Desc.dbOpenMode == CL5_OPEN_NORMAL) + { + rc = _cl5GetEntryCount (*dbFile); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, + "_cl5NewDBFile: failed to get entry count\n"); + goto done; + } + } + +done: + if (rc != CL5_SUCCESS) + { + if (dbFile) + _cl5DBCloseFile ((void**)dbFile); + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&name); + + slapi_ch_free ((void**)dbFile); + } + + return rc; +} + +static void _cl5DBCloseFile (void **data) +{ + CL5DBFile *file; + char fullpathname[MAXPATHLEN]; + + PR_ASSERT (data); + + file = *(CL5DBFile**)data; + + /* close the file */ + /* if this is normal close or close after import, update entry count */ + if ((s_cl5Desc.dbOpenMode == CL5_OPEN_NORMAL && s_cl5Desc.dbState == CL5_STATE_CLOSING) || + s_cl5Desc.dbOpenMode == CL5_OPEN_LDIF2CL) + { + _cl5WriteEntryCount (file); + _cl5WriteRUV (file, PR_TRUE); + _cl5WriteRUV (file, PR_FALSE); + } + + /* close file */ + if (file->db) + file->db->close(file->db, 0); + + if (file->flags & DB_FILE_DELETED) + { + PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, file->name); + if (PR_Delete(fullpathname) != PR_SUCCESS) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBCloseFile: " + "failed to remove (%s) file; NSPR error - %d\n", file->name, PR_GetError ()); + + } + } + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&file->name); + slapi_ch_free ((void**)&file->replName); + slapi_ch_free ((void**)&file->replGen); + if (file->sema) { + PR_CloseSemaphore (file->sema); + PR_DeleteSemaphore (file->semaName); + file->sema = NULL; + } + slapi_ch_free ((void**)&file->semaName); + + slapi_ch_free (data); +} + +static int _cl5GetDBFile (Object *replica, Object **obj) +{ + char *fileName; + + PR_ASSERT (replica && obj); + + fileName = _cl5Replica2FileName (replica); + + *obj = objset_find(s_cl5Desc.dbFiles, _cl5CompareDBFile, fileName); + slapi_ch_free ((void**)&fileName); + if (*obj) + { + return CL5_SUCCESS; + } + else + { + return CL5_NOTFOUND; + } +} + +static int _cl5GetDBFileByReplicaName (const char *replName, const char *replGen, + Object **obj) +{ + char *fileName; + + PR_ASSERT (replName && replGen && obj); + + fileName = _cl5MakeFileName (replName, replGen); + + *obj = objset_find(s_cl5Desc.dbFiles, _cl5CompareDBFile, fileName); + slapi_ch_free ((void**)&fileName); + if (*obj) + { + return CL5_SUCCESS; + } + else + { + return CL5_NOTFOUND; + } +} + +static void _cl5DBDeleteFile (Object *obj) +{ + CL5DBFile *file; + + PR_ASSERT (obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + file->flags |= DB_FILE_DELETED; + objset_remove_obj(s_cl5Desc.dbFiles, obj); + object_release (obj); +} + +static void _cl5DBFileInitialized (Object *obj) +{ + CL5DBFile *file; + + PR_ASSERT (obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + file->flags |= DB_FILE_INIT; +} + +static int _cl5CompareDBFile (Object *el1, const void *el2) +{ + CL5DBFile *file; + const char *name; + + PR_ASSERT (el1 && el2); + + file = (CL5DBFile*) object_get_data (el1); + name = (const char*) el2; + return ((file->flags & DB_FILE_INIT) ? strcmp (file->name, name) : 1); +} + +static int _cl5CopyDBFiles (const char *srcDir, const char *destDir, Object **replicas) +{ + char srcFile [MAXPATHLEN + 1]; + char destFile[MAXPATHLEN + 1]; + int rc; + Object *obj; + CL5DBFile *file; + + /* ONREPL currently, dbidlist is ignored because db code can't handle discrepancy between + transaction log and present files; this should change before 5.0 ships */ + obj = objset_first_obj (s_cl5Desc.dbFiles); + while (obj) + { + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + PR_snprintf(srcFile, MAXPATHLEN, "%s/%s", srcDir, file->name); + PR_snprintf(destFile, MAXPATHLEN, "%s/%s", destDir, file->name); + rc = copyfile(srcFile, destFile, 0, FILE_CREATE_MODE); + if (rc != 0) + { + object_release (obj); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5CopyDBFiles: failed to copy %s from %s to %s\n", + file, srcDir, destDir); + return CL5_SYSTEM_ERROR; + } + + obj = objset_next_obj (s_cl5Desc.dbFiles, obj); + } + + return CL5_SUCCESS; +} + +/* + * return 1: true (the "filename" ends with "ext") + * return 0: false + */ +static int _cl5FileEndsWith(const char *filename, const char *ext) +{ + char *p = NULL; + int flen = strlen(filename); + int elen = strlen(ext); + if (0 == flen || 0 == elen) + { + return 0; + } + p = strstr(filename, ext); + if (NULL == p) + { + return 0; + } + if (p - filename + elen == flen) + { + return 1; + } + return 0; +} + +static int _cl5ExportFile (PRFileDesc *prFile, Object *obj) +{ + int rc; + void *iterator = NULL; + slapi_operation_parameters op = {0}; + char *buff; + PRInt32 len, wlen; + CL5Entry entry; + CL5DBFile *file; + + PR_ASSERT (prFile && obj); + + file = (CL5DBFile*)object_get_data (obj); + PR_ASSERT (file); + + ruv_dump (file->purgeRUV, "clpurgeruv", prFile); + ruv_dump (file->maxRUV, "clmaxruv", prFile); + slapi_write_buffer (prFile, "\n", strlen("\n")); + + entry.op = &op; + rc = _cl5GetFirstEntry (obj, &entry, &iterator, NULL); + while (rc == CL5_SUCCESS) + { + rc = _cl5Operation2LDIF (&op, file->replGen, &buff, &len); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5ExportLDIF: failed to convert operation to ldif\n"); + operation_parameters_done (&op); + break; + } + + wlen = slapi_write_buffer (prFile, buff, len); + slapi_ch_free((void **)&buff); + if (wlen < len) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5ExportLDIF: failed to write to ldif file\n"); + rc = CL5_SYSTEM_ERROR; + operation_parameters_done (&op); + break; + } + + cl5_operation_parameters_done (&op); + + rc = _cl5GetNextEntry (&entry, iterator); + } + + cl5_operation_parameters_done (&op); + + if (iterator) + cl5DestroyIterator (iterator); + + if (rc != CL5_NOTFOUND) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "_cl5ExportLDIF: failed to retrieve changelog entry\n"); + } + else + { + rc = CL5_SUCCESS; + } + + return rc; +} + +static PRBool _cl5ReplicaInList (Object *replica, Object **replicas) +{ + int i; + + PR_ASSERT (replica && replicas); + + /* ONREPL I think it should be sufficient to just compare replica pointers */ + for (i=0; replicas[i]; i++) + { + if (replica == replicas[i]) + return PR_TRUE; + } + + return PR_FALSE; +} + +static char* _cl5GetHelperEntryKey (int type, char *csnStr) +{ + CSN *csn= csn_new(); + char *rt; + + csn_set_time(csn, type); + csn_set_replicaid(csn, 0); + + rt = csn_as_string(csn, PR_FALSE, csnStr); + csn_free(&csn); + + return rt; +} + +static Object* _cl5GetReplica (const slapi_operation_parameters *op, const char* replGen) +{ + Slapi_DN *sdn; + Object *replObj; + Replica *replica; + char *newGen; + + PR_ASSERT (op && replGen); + + sdn = slapi_sdn_new_dn_byref(op->target_address.dn); + + replObj = replica_get_replica_from_dn (sdn); + if (replObj) + { + /* check to see if replica generation has not change */ + replica = (Replica*)object_get_data (replObj); + PR_ASSERT (replica); + newGen = replica_get_generation (replica); + PR_ASSERT (newGen); + if (strcmp (replGen, newGen) != 0) + { + object_release (replObj); + replObj = NULL; + } + + slapi_ch_free ((void**)&replGen); + } + + slapi_sdn_free (&sdn); + + return replObj; +} + +int +cl5_is_diskfull() +{ + int rc; + PR_Lock(cl5_diskfull_lock); + rc = cl5_diskfull_flag; + PR_Unlock(cl5_diskfull_lock); + return rc; +} + +static void +cl5_set_diskfull() +{ + PR_Lock(cl5_diskfull_lock); + cl5_diskfull_flag = 1; + PR_Unlock(cl5_diskfull_lock); +} + +static void +cl5_set_no_diskfull() +{ + PR_Lock(cl5_diskfull_lock); + cl5_diskfull_flag = 0; + PR_Unlock(cl5_diskfull_lock); +} + +int +cl5_diskspace_is_available() +{ + int rval = 1; + +#if defined( OS_solaris ) || defined( hpux ) + struct statvfs fsbuf; + if (statvfs(s_cl5Desc.dbDir, &fsbuf) < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: Cannot get file system info\n"); + rval = 0; + } + else + { + unsigned long fsiz = fsbuf.f_bavail * fsbuf.f_frsize; + if (fsiz < NO_DISK_SPACE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: No enough diskspace for changelog: (%u bytes free)\n", fsiz); + rval = 0; + } + else if (fsiz > MIN_DISK_SPACE) + { + /* assume recovered */ + cl5_set_no_diskfull(); + } + } +#endif +#if defined( linux ) + struct statfs fsbuf; + if (statfs(s_cl5Desc.dbDir, &fsbuf) < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: Cannot get file system info\n"); + rval = 0; + } + else + { + unsigned long fsiz = fsbuf.f_bavail * fsbuf.f_bsize; + if (fsiz < NO_DISK_SPACE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "cl5_diskspace_is_available: No enough diskspace for changelog: (%u bytes free)\n", fsiz); + rval = 0; + } + else if (fsiz > MIN_DISK_SPACE) + { + /* assume recovered */ + cl5_set_no_diskfull(); + } + } +#endif + return rval; +} diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h new file mode 100644 index 00000000..49296df2 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_api.h @@ -0,0 +1,478 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_api.h - interface to 5.0 changelog */ + +#ifndef CL5_API_H +#define CL5_API_H + +#include "repl5.h" +#include "repl5_prot_private.h" + +#define CL5_TYPE "Changelog5" /* changelog type */ +#define VERSION_SIZE 127 /* size of the buffer to hold changelog version */ +#define CL5_DEFAULT_CONFIG -1 /* value that indicates to changelog to use default */ +#define CL5_STR_IGNORE "-1" /* tels function to ignore this parameter */ +#define CL5_NUM_IGNORE -1 /* tels function to ignore this parameter */ +#define CL5_STR_UNLIMITED "0" /* represent unlimited value (trimming ) */ +#define CL5_NUM_UNLIMITED 0 /* represent unlimited value (trimming ) */ + +#define CL5_OS_ERR_IS_DISKFULL(err) ((err)==ENOSPC || (err)==EFBIG) + +/***** Data Structures *****/ + +/* changelog configuration structure */ +typedef struct cl5dbconfig +{ + size_t cacheSize; /* cache size in bytes */ + PRBool durableTrans; /* flag that tells not to sync log when trans commits */ + PRInt32 checkpointInterval; /* checkpoint interval in seconds */ + PRBool circularLogging; /* flag to archive and trancate log */ + size_t pageSize; /* page size in bytes */ + size_t logfileSize; /* maximum log size in bytes */ + size_t maxTxnSize; /* maximum txn table size in count*/ + PRInt32 fileMode; /* file mode */ + PRBool verbose; /* Get libdb to exhale debugging info */ + PRBool debug; /* Will libdb emit debugging info into our log ? */ + PRInt32 tricklePercentage; /* guaranteed percentage of clean cache pages; 0 - 100 */ + PRInt32 spinCount; /* DB Mutex spin count */ + PRUint32 nb_lock_config; /* Number of locks in the DB lock table. New in 5.1 */ +/* The next 2 parameters are needed for configuring the changelog cache. New in 5.1 */ + PRUint32 maxChCacheEntries; + PRUint32 maxChCacheSize; + PRUint32 maxConcurrentWrites; /* 6.2 max number of concurrent cl writes */ +} CL5DBConfig; + +/* changelog entry format */ +typedef struct cl5entry +{ + slapi_operation_parameters *op; /* operation applied to the server */ + time_t time; /* time added to the cl; used for trimming */ +} CL5Entry; + +/* default values for the changelog configuration structure above */ +/* + * For historical reasons, dbcachesize refers to number of bytes at the DB level, + * whereas cachesize refers to number of entries at the changelog cache level (cachememsize is the + * one refering to number of bytes at the changelog cache level) + */ +#define CL5_DEFAULT_CONFIG_DB_DBCACHESIZE 10485760 /* 10M bytes */ +#define CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS 1 +#define CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL 60 +#define CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING 1 +#define CL5_DEFAULT_CONFIG_DB_PAGE_SIZE 8*1024 +#define CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE 0 +#define CL5_DEFAULT_CONFIG_DB_VERBOSE 0 +#define CL5_DEFAULT_CONFIG_DB_DEBUG 0 +#define CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE 40 +#define CL5_DEFAULT_CONFIG_DB_SPINCOUNT 0 +#define CL5_DEFAULT_CONFIG_DB_TXN_MAX 200 +#define CL5_DEFAULT_CONFIG_CACHESIZE 3000 /* number of entries */ +#define CL5_DEFAULT_CONFIG_CACHEMEMSIZE 1048576 /* 1 M bytes */ +#define CL5_DEFAULT_CONFIG_NB_LOCK 1000 /* Number of locks in the lock table of the DB */ + +/* + * Small number of concurrent writes degradate the throughput. + * Large one increases deadlock. + */ +#ifdef SOLARIS +#define CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES 10 +#else +#define CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES 2 +#endif + + +#define CL5_MIN_DB_DBCACHESIZE 524288 /* min 500K bytes */ +#define CL5_MIN_CACHESIZE 500 /* min number of entries */ +#define CL5_MIN_CACHEMEMSIZE 262144 /* min 250K bytes */ +#define CL5_MIN_NB_LOCK 1000 /* The minimal number of locks in the DB (Same as default) */ + +/* data structure that allows iteration through changelog */ +typedef struct cl5replayiterator CL5ReplayIterator; + +/* changelog state */ +typedef enum +{ + CL5_STATE_NONE, /* changelog has not been initialized */ + CL5_STATE_CLOSING, /* changelog is about to close; all threads must exit */ + CL5_STATE_CLOSED, /* changelog has been initialized, but not opened, or open and then closed */ + CL5_STATE_OPEN /* changelog is opened */ +} CL5State; + +/* error codes */ +enum +{ + CL5_SUCCESS, /* successful operation */ + CL5_BAD_DATA, /* invalid parameter passed to the function */ + CL5_BAD_FORMAT, /* db data has unexpected format */ + CL5_BAD_STATE, /* changelog is in an incorrect state for attempted operation */ + CL5_BAD_DBVERSION, /* changelog has invalid dbversion */ + CL5_DB_ERROR, /* database error */ + CL5_NOTFOUND, /* requested entry or value was not found */ + CL5_MEMORY_ERROR, /* memory allocation failed */ + CL5_SYSTEM_ERROR, /* NSPR error occured, use PR_Error for furhter info */ + CL5_CSN_ERROR, /* CSN API failed */ + CL5_RUV_ERROR, /* RUV API failed */ + CL5_OBJSET_ERROR, /* namedobjset api failed */ + CL5_PURGED_DATA, /* requested data has been purged */ + CL5_MISSING_DATA, /* data should be in the changelog, but is missing */ + CL5_UNKNOWN_ERROR /* unclassified error */ +}; + +/***** Module APIs *****/ + +/* Name: cl5Init + Description: initializes changelog module; must be called by a single thread + before any function of the module. + Parameters: none + Return: CL5_SUCCESS if function is successful; + CL5_BAD_DATA if invalid directory is passed; + CL5_SYSTEM error if NSPR call fails. + */ +int cl5Init (); + +/* Name: cl5Cleanup + Description: performs cleanup of the changelog module. Must be called by a single + thread. It will closed db if it is still open. + Parameters: none + Return: none + */ +void cl5Cleanup (); + +/* Name: cl5Open + Description: opens changelog ; must be called after changelog is + initialized using cl5Init. It is thread safe and the second + call is ignored. + Parameters: dir - changelog dir + config - db configuration parameters; currently not used + openMode - open mode + Return: CL5_SUCCESS if successfull; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_DBVERSION if dbversion file is missing or has unexpected data + CL5_SYSTEM_ERROR if NSPR error occured (during db directory creation); + CL5_MEMORY_ERROR if memory allocation fails; + CL5_DB_ERROR if db initialization or open fails. + */ +int cl5Open (const char *dir, const CL5DBConfig *config); + +/* Name: cl5Close + Description: closes changelog and cleanups changelog module; waits until + all threads are done using changelog + Parameters: none + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if db is not in the open state; + CL5_SYSTEM_ERROR if NSPR call fails + */ +int cl5Close (); + +/* Name: cl5Delete + Description: removes changelog + Parameters: dir - changelog directory + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not in closed state; + CL5_BAD_DATA if invalid directory supplied + CL5_SYSTEM_ERROR if NSPR call fails + */ +int cl5Delete (const char *dir); + +/* Name: cl5OpenDB + Description: opens changelog file for specified file + Parameters: replica - replica whose file we wish to open + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + */ +int cl5OpenDB (Object *replica); + +/* Name: cl5CloseDB + Description: closes changelog file for the specified replica + Parameters: replica - replica whose file we wish to close + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5CloseDB (Object *replica); + +/* Name: cl5DeleteDB + Description: asynchronously removes changelog file for the specified replica. + The file is physically removed when it is no longer in use. + This function is called when a backend is removed or reloaded. + Parameters: replica - replica whose file we wish to delete + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND - nothing is known about specified database + */ +int cl5DeleteDB (Object *replica); + +/* Name: cl5DeleteDBSync + Description: The same as cl5DeleteDB except the function does not return + until the file is removed. +*/ +int cl5DeleteDBSync (Object *replica); + +/* Name: cl5GetUpperBoundRUV + Description: retrieves vector that represent the upper bound of changes + stored in the changelog for the replica. + Parameters: r - replica for which the vector is requested + ruv - contains a copy of the upper bound ruv if function is successful; + unchanged otherwise. It is responsobility pf the caller to free + the ruv when it is no longer is in use + Return: CL5_SUCCESS if function is successfull + CL5_BAD_STATE if the changelog is not initialized; + CL5_BAD_DATA - if NULL id is supplied + CL5_NOTFOUND, if changelog file for replica is not found + */ +int cl5GetUpperBoundRUV (Replica *r, RUV **ruv); + +/* Name: cl5Backup + Description: makes a backup of the changelog including *.db2, + log files, and dbversion. Can be called with the changelog in either open or + closed state. + Parameters: bkDir - directory to which the data is backed up; + created if it does not exist + replicas - optional list of replicas whose changes should be backed up; + if the list is NULL, entire changelog is backed up. + Return: CL5_SUCCESS if function is successful; + CL5_BAD_DATA if invalid directory is passed; + CL5_BAD_STATE if changelog has not been initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call or file copy failes. + */ +int cl5Backup (const char *bkDir, Object **replicas); + +/* Name: cl5Restore + Description: restores changelog from the backed up copy. Changelog must be ibnitalized and closed. + Parameters: clDir - changelog dir + bkDir - directory that contains the backup + replicas - optional list of replicas whose changes should be recovered; + if the list is NULL, entire changelog is recovered. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not initialized; + CL5_DB_ERROR if db call fails; + CL5_SYSTEM_ERROR if NSPR call of file copy fails + */ +int cl5Restore (const char *clDir, const char *bkDir, Object **replicas); + +/* Name: cl5ExportLDIF + Description: dumps changelog to an LDIF file; changelog can be open or closed. + Parameters: clDir - changelog dir + ldifFile - full path to ldif file to write + replicas - optional list of replicas whose changes should be exported; + if the list is NULL, entire changelog is exported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is not initialized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ExportLDIF (const char *ldifFile, Object **replicas); + +/* Name: cl5ImportLDIF + Description: imports ldif file into changelog; changelog must be in the closed state + Parameters: clDir - changelog dir + ldifFile - absolute path to the ldif file to import + replicas - optional list of replicas whose data should be imported; + if the list is NULL, all data in the file is imported. + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if changelog is open or not inititalized; + CL5_DB_ERROR if db api fails; + CL5_SYSTEM_ERROR if NSPR call fails; + CL5_MEMORY_ERROR if memory allocation fials. + */ +int cl5ImportLDIF (const char *clDir, const char *ldifFile, Object **replicas); + +/* Name: cl5GetState + Description: returns database state + Parameters: none + Return: changelog state + */ + +int cl5GetState (); + +/* Name: cl5ConfigTrimming + Description: sets changelog trimming parameters + Parameters: maxEntries - maximum number of entries in the log; + maxAge - maximum entry age; + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if changelog has not been open + */ +int cl5ConfigTrimming (int maxEntries, const char *maxAge); + +/* Name: cl5GetOperation + Description: retireves operation specified by its csn and databaseid + Parameters: op - must contain csn and databaseid; the rest of data is + filled if function is successfull + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_NOTFOUND if entry was not found; + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if db data format does not match entry format. + */ +int cl5GetOperation (Object *replica, slapi_operation_parameters *op); + +/* Name: cl5GetFirstOperation + Description: retrieves first operation for a particular database + replica - replica for which the operation should be retrieved. + Parameters: op - buffer to store the operation; + iterator - to be passed to the call to cl5GetNextOperation + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if operation is NULL + CL5_BAD_STATE, if changelog is not open + CL5_DB_ERROR, if db call fails + */ +int cl5GetFirstOperation (Object *replica, slapi_operation_parameters *op, void **iterator); + +/* Name: cl5GetNextOperation + Description: retrieves the next op from the changelog as defined by the iterator + Parameters: replica - replica for which the operation should be retrieved. + op - returned operation, if function is successful + iterator - in: identifies op to retrieve; out: identifies next op + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if invalid parameter is supplied + CL5_BAD_STATE, if changelog is not open + CL5_NOTFOUND, empty changelog + CL5_DB_ERROR, if db call fails + */ +int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator); + +/* Name: cl5DestroyIterator + Description: destroys iterator once iteration through changelog is done + Parameters: iterator - iterator to destroy + Return: CL5_SUCCESS, if successful + CL5_BADDATA, if invalid parameters is supplied + CL5_BAD_STATE, if changelog is not open + CL5_DB_ERROR, if db call fails + */ +void cl5DestroyIterator (void *iterator); + +/* Name: cl5WriteOperation + Description: writes operation to changelog + Parameters: repl_name - name of the replica to which operation applies + repl_gen - replica generation for the operation + !!!Note that we pass name and generation rather than + replica object since generation can change while operation + is in progress (if the data is reloaded). !!! + op - operation to write + local - this is a non-replicated operation + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid op is passed; + CL5_BAD_STATE if db has not been initialized; + CL5_MEMORY_ERROR if memory allocation failed; + CL5_DB_ERROR if any other db error occured; + */ +int cl5WriteOperation(const char *repl_name, const char *repl_gen, + const slapi_operation_parameters *op, PRBool local); + +/* Name: cl5CreateReplayIterator + Description: creates an iterator that allows to retireve changes that should + to be sent to the consumer identified by ruv The iteration is peformed by + repeated calls to cl5GetNextOperationToReplay. + Parameters: replica - replica whose data we wish to iterate; + ruv - consumer ruv; + iterator - iterator to be passed to cl5GetNextOperationToReplay call + Return: CL5_SUCCESS, if function is successfull; + CL5_MISSING_DATA, if data that should be in the changelog is missing + CL5_PURGED_DATA, if some data that consumer needs has been purged. + Note that the iterator can be non null if the supplier contains + some data that needs to be sent to the consumer + CL5_NOTFOUND if the consumer is up to data with respect to the supplier + CL5_BAD_DATA if invalid parameter is passed; + CL5_BAD_STATE if db has not been open; + CL5_DB_ERROR if any other db error occured; + CL5_MEMORY_ERROR if memory allocation fails. + */ +int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *ruv, + CL5ReplayIterator **iterator); + +/* Name: cl5GetNextOperationToReplay + Description: retrieves next operation to be sent to the consumer and + that was created on a particular master. Consumer and master info + is encoded in the iterator parameter that must be created by calling + to cl5CreateIterator. + Parameters: iterator - iterator that identifies next entry to retrieve; + op - operation retireved if function is successful + Return: CL5_SUCCESS if function is successfull; + CL5_BAD_DATA if invalid parameter is passed; + CL5_NOTFOUND if end of iteration list is reached + CL5_DB_ERROR if any other db error occured; + CL5_BADFORMAT if data in db is of unrecognized format; + CL5_MEMORY_ERROR if memory allocation fails. + */ +int cl5GetNextOperationToReplay (CL5ReplayIterator *iterator, + CL5Entry *entry); + +/* Name: cl5DestroyReplayIterator + Description: destorys iterator + Parameters: iterator - iterator to destory + Return: none + */ +void cl5DestroyReplayIterator (CL5ReplayIterator **iterator); + +/* Name: cl5DeleteOnClose + Description: marks changelog for deletion when it is closed + Parameters: flag; if flag = 1 then delete else don't + Return: none + */ + +void cl5DeleteOnClose (PRBool rm); + +/* Name: cl5GetDir + Description: returns changelog directory; must be freed by the caller; + Parameters: none + Return: copy of the directory; caller needs to free the string + */ + +char *cl5GetDir (); + +/* Name: cl5Exist + Description: checks if a changelog exists in the specified directory + Parameters: clDir - directory to check; + Return: 1 - if changelog exists; 0 - otherwise + */ + +PRBool cl5Exist (const char *clDir); + +/* Name: cl5GetOperationCount + Description: returns number of entries in the changelog. The changelog must be + open for the value to be meaningful. + Parameters: replica - optional parameter that specifies the replica whose operations + we wish to count; if NULL all changelog entries are counted + Return: number of entries in the changelog + */ + +int cl5GetOperationCount (Object *replica); + +/* Name: cl5_operation_parameters_done + Description: frees all parameters that are not freed by operation_parameters_done + function in the server. + + */ + +void cl5_operation_parameters_done (struct slapi_operation_parameters *sop); + +/* Name: cl5CreateDirIfNeeded + Description: Create the directory if it doesn't exist yet + Parameters: dir - Contains the name of the directory to create. Must not be NULL + Return: CL5_SUCCESS if succeeded or existed, + CL5_SYSTEM_ERROR if failed. +*/ + +int cl5CreateDirIfNeeded (const char *dir); +int cl5DBData2Entry (const char *data, PRUint32 len, CL5Entry *entry); + +PRBool cl5HelperEntry (const char *csnstr, CSN *csn); +CSN** cl5BuildCSNList (const RUV *consRuv, const RUV *supRuv); +void cl5DestroyCSNList (CSN*** csns); + +int cl5_is_diskfull(); +int cl5_diskspace_is_available(); + +#endif diff --git a/ldap/servers/plugins/replication/cl5_clcache.c b/ldap/servers/plugins/replication/cl5_clcache.c new file mode 100644 index 00000000..585a7266 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_clcache.c @@ -0,0 +1,910 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2003 Netscape Communications Corporation + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "errno.h" /* ENOMEM, EVAL used by Berkeley DB */ +#include "db.h" /* Berkeley DB */ +#include "cl5.h" /* changelog5Config */ +#include "cl5_clcache.h" + +/* + * Constants for the buffer pool: + * + * DEFAULT_CLC_BUFFER_PAGE_COUNT + * Little performance boost if it is too small. + * + * DEFAULT_CLC_BUFFER_PAGE_SIZE + * Its value is determined based on the DB requirement that + * the buffer size should be the multiple of 1024. + */ +#define DEFAULT_CLC_BUFFER_COUNT_MIN 10 +#define DEFAULT_CLC_BUFFER_COUNT_MAX 0 +#define DEFAULT_CLC_BUFFER_PAGE_COUNT 32 +#define DEFAULT_CLC_BUFFER_PAGE_SIZE 1024 + +static enum { + CLC_STATE_READY = 0, /* ready to iterate */ + CLC_STATE_UP_TO_DATE, /* remote RUV already covers the CSN */ + CLC_STATE_CSN_GT_RUV, /* local RUV doesn't conver the CSN */ + CLC_STATE_NEW_RID, /* unknown RID to local RUVs */ + CLC_STATE_UNSAFE_RUV_CHANGE,/* (RUV1 < maxcsn-in-buffer) && (RUV1 < RUV1') */ + CLC_STATE_DONE, /* no more change */ + CLC_STATE_ABORTING /* abort replication session */ +}; + +typedef struct clc_busy_list CLC_Busy_List; + +struct csn_seq_ctrl_block { + ReplicaId rid; /* RID this block serves */ + CSN *consumer_maxcsn; /* Don't send CSN <= this */ + CSN *local_maxcsn; /* Don't send CSN > this */ + CSN *prev_local_maxcsn; /* */ + int state; /* CLC_STATE_* */ +}; + +/* + * Each cl5replayiterator acquires a buffer from the buffer pool + * at the beginning of a replication session, and returns it back + * at the end. + */ +struct clc_buffer { + char *buf_agmt_name; /* agreement acquired this buffer */ + ReplicaId buf_consumer_rid; /* help checking threshold csn */ + const RUV *buf_consumer_ruv; /* used to skip change */ + const RUV *buf_local_ruv; /* used to refresh local_maxcsn */ + + /* + * fields for retriving data from DB + */ + int buf_state; + CSN *buf_current_csn; + int buf_load_flag; /* db flag DB_MULTIPLE_KEY, DB_SET, DB_NEXT */ + DBC *buf_cursor; + DBT buf_key; /* current csn string */ + DBT buf_data; /* data retrived from db */ + void *buf_record_ptr; /* ptr to the current record in data */ + CSN *buf_missing_csn; /* used to detect persistent missing of CSN */ + + /* fields for control the CSN sequence sent to the consumer */ + struct csn_seq_ctrl_block *buf_cscbs [MAX_NUM_OF_MASTERS]; + int buf_num_cscbs; /* number of csn sequence ctrl blocks */ + + /* fields for debugging stat */ + int buf_load_cnt; /* number of loads for session */ + int buf_record_cnt; /* number of changes for session */ + int buf_record_skipped; /* number of changes skipped */ + + /* + * fields that should be accessed via bl_lock or pl_lock + */ + CLC_Buffer *buf_next; /* next buffer in the same list */ + CLC_Busy_List *buf_busy_list; /* which busy list I'm in */ +}; + +/* + * Each changelog has a busy buffer list + */ +struct clc_busy_list { + PRLock *bl_lock; + DB *bl_db; /* changelog db handle */ + CLC_Buffer *bl_buffers; /* busy buffers of this list */ + CLC_Busy_List *bl_next; /* next busy list in the pool */ +}; + +/* + * Each process has a buffer pool + */ +struct clc_pool { + PRRWLock *pl_lock; /* cl writer and agreements */ + DB_ENV **pl_dbenv; /* pointer to DB_ENV for all the changelog files */ + CLC_Busy_List *pl_busy_lists; /* busy buffer lists, one list per changelog file */ + int pl_buffer_cnt_now; /* total number of buffers */ + int pl_buffer_cnt_min; /* free a newly returned buffer if _now > _min */ + int pl_buffer_cnt_max; /* no use */ + int pl_buffer_default_pages; /* num of pages in a new buffer */ +}; + +/* static variables */ +static struct clc_pool *_pool = NULL; /* process's buffer pool */ + +/* static prototypes */ +static int clcache_adjust_anchorcsn ( CLC_Buffer *buf ); +static void clcache_refresh_consumer_maxcsns ( CLC_Buffer *buf ); +static int clcache_refresh_local_maxcsns ( CLC_Buffer *buf ); +static int clcache_skip_change ( CLC_Buffer *buf ); +static int clcache_load_buffer_bulk ( CLC_Buffer *buf, int flag ); +static int clcache_open_cursor ( DB_TXN *txn, CLC_Buffer *buf, DBC **cursor ); +static int clcache_cursor_get ( DBC *cursor, CLC_Buffer *buf, int flag ); +static struct csn_seq_ctrl_block *clcache_new_cscb (); +static void clcache_free_cscb ( struct csn_seq_ctrl_block ** cscb ); +static CLC_Buffer *clcache_new_buffer ( ReplicaId consumer_rid ); +static void clcache_delete_buffer ( CLC_Buffer **buf ); +static CLC_Busy_List *clcache_new_busy_list (); +static void clcache_delete_busy_list ( CLC_Busy_List **bl ); +static int clcache_enqueue_busy_list( DB *db, CLC_Buffer *buf ); +static void csn_dup_or_init_by_csn ( CSN **csn1, CSN *csn2 ); + +/* + * Initiates the process buffer pool. This should be done + * once and only once when process starts. + */ +int +clcache_init ( DB_ENV **dbenv ) +{ + _pool = (struct clc_pool*) slapi_ch_calloc ( 1, sizeof ( struct clc_pool )); + _pool->pl_dbenv = dbenv; + _pool->pl_buffer_cnt_min = DEFAULT_CLC_BUFFER_COUNT_MIN; + _pool->pl_buffer_cnt_max = DEFAULT_CLC_BUFFER_COUNT_MAX; + _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_COUNT_MAX; + _pool->pl_lock = PR_NewRWLock (PR_RWLOCK_RANK_NONE, "clcache_pl_lock"); + return 0; +} + +/* + * This is part of a callback function when changelog configuration + * is read or updated. + */ +void +clcache_set_config ( CL5DBConfig *config ) +{ + if ( config == NULL ) return; + + PR_RWLock_Wlock ( _pool->pl_lock ); + + _pool->pl_buffer_cnt_max = config->maxChCacheEntries; + + /* + * According to http://www.sleepycat.com/docs/api_c/dbc_get.html, + * data buffer should be a multiple of 1024 bytes in size + * for DB_MULTIPLE_KEY operation. + */ + _pool->pl_buffer_default_pages = config->maxChCacheSize / DEFAULT_CLC_BUFFER_PAGE_SIZE + 1; + _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_PAGE_COUNT; + if ( _pool->pl_buffer_default_pages <= 0 ) { + _pool->pl_buffer_default_pages = DEFAULT_CLC_BUFFER_PAGE_COUNT; + } + + PR_RWLock_Unlock ( _pool->pl_lock ); +} + +/* + * Gets the pointer to a thread dedicated buffer, or allocates + * a new buffer if there is no buffer allocated yet for this thread. + * + * This is called when a cl5replayiterator is created for + * a replication session. + */ +int +clcache_get_buffer ( CLC_Buffer **buf, DB *db, ReplicaId consumer_rid, const RUV *consumer_ruv, const RUV *local_ruv ) +{ + int rc = 0; + + if ( buf == NULL ) return CL5_BAD_DATA; + + *buf = NULL; + + if ( NULL != ( *buf = (CLC_Buffer*) get_thread_private_cache()) ) { + (*buf)->buf_state = CLC_STATE_READY; + (*buf)->buf_load_cnt = 0; + (*buf)->buf_record_cnt = 0; + (*buf)->buf_record_skipped = 0; + (*buf)->buf_cursor = NULL; + (*buf)->buf_num_cscbs = 0; + } + else { + *buf = clcache_new_buffer ( consumer_rid ); + if ( *buf ) { + if ( 0 == clcache_enqueue_busy_list ( db, *buf ) ) { + set_thread_private_cache ( (void*) (*buf) ); + } + else { + clcache_delete_buffer ( buf ); + } + } + } + + if ( NULL != *buf ) { + (*buf)->buf_consumer_ruv = consumer_ruv; + (*buf)->buf_local_ruv = local_ruv; + } + else { + slapi_log_error ( SLAPI_LOG_FATAL, get_thread_private_agmtname(), + "clcache_get_buffer: can't allocate new buffer\n" ); + rc = ENOMEM; + } + + return rc; +} + +/* + * Returns a buffer back to the buffer pool. + */ +void +clcache_return_buffer ( CLC_Buffer **buf ) +{ + int i; + + slapi_log_error ( SLAPI_LOG_REPL, (*buf)->buf_agmt_name, + "session end: state=%d load=%d sent=%d skipped=%d\n", + (*buf)->buf_state, + (*buf)->buf_load_cnt, + (*buf)->buf_record_cnt - (*buf)->buf_record_skipped, + (*buf)->buf_record_skipped ); + + for ( i = 0; i < (*buf)->buf_num_cscbs; i++ ) { + clcache_free_cscb ( &(*buf)->buf_cscbs[i] ); + } + (*buf)->buf_num_cscbs = 0; + + if ( (*buf)->buf_cursor ) { + + (*buf)->buf_cursor->c_close ( (*buf)->buf_cursor ); + (*buf)->buf_cursor = NULL; + } +} + +/* + * Loads a buffer from DB. + * + * anchorcsn - passed in for the first load of a replication session; + * flag - DB_SET to load in the key CSN record. + * DB_NEXT to load in the records greater than key CSN. + * return - DB error code instead of cl5 one because of the + * historic reason. + */ +int +clcache_load_buffer ( CLC_Buffer *buf, CSN *anchorcsn, int flag ) +{ + int rc = 0; + + clcache_refresh_local_maxcsns ( buf ); + + /* Set the loading key */ + if ( anchorcsn ) { + clcache_refresh_consumer_maxcsns ( buf ); + buf->buf_load_flag = DB_MULTIPLE_KEY; + csn_as_string ( anchorcsn, 0, (char*)buf->buf_key.data ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "session start: anchorcsn=%s\n", (char*)buf->buf_key.data ); + } + else if ( csn_get_time(buf->buf_current_csn) == 0 ) { + /* time == 0 means this csn has never been set */ + rc = DB_NOTFOUND; + } + else if ( clcache_adjust_anchorcsn ( buf ) != 0 ) { + rc = DB_NOTFOUND; + } + else { + csn_as_string ( buf->buf_current_csn, 0, (char*)buf->buf_key.data ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "load next: anchorcsn=%s\n", (char*)buf->buf_key.data ); + } + + if ( rc == 0 ) { + + buf->buf_state = CLC_STATE_READY; + rc = clcache_load_buffer_bulk ( buf, flag ); + + /* Reset some flag variables */ + if ( rc == 0 ) { + int i; + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + buf->buf_cscbs[i]->state = CLC_STATE_READY; + } + } + else if ( anchorcsn ) { + /* Report error only when the missing is persistent */ + if ( buf->buf_missing_csn && csn_compare (buf->buf_missing_csn, anchorcsn) == 0 ) { + slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name, + "Can't locate CSN %s in the changelog (DB rc=%d). The consumer may need to be reinitialized.\n", + (char*)buf->buf_key.data, rc ); + } + else { + csn_dup_or_init_by_csn (&buf->buf_missing_csn, anchorcsn); + } + } + } + if ( rc != 0 ) { + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "clcache_load_buffer: rc=%d\n", rc ); + } + + return rc; +} + +static int +clcache_load_buffer_bulk ( CLC_Buffer *buf, int flag ) +{ + DB_TXN *txn = NULL; + DBC *cursor = NULL; + int rc; + + /* txn control seems not improving anything so turn it off */ + /* + if ( *(_pool->pl_dbenv) ) { + txn_begin( *(_pool->pl_dbenv), NULL, &txn, 0 ); + } + */ + + PR_Lock ( buf->buf_busy_list->bl_lock ); + if ( 0 == ( rc = clcache_open_cursor ( txn, buf, &cursor )) ) { + + if ( flag == DB_NEXT ) { + /* For bulk read, position the cursor before read the next block */ + rc = cursor->c_get ( cursor, + & buf->buf_key, + & buf->buf_data, + DB_SET ); + } + + /* + * Continue if the error is no-mem since we don't need to + * load in the key record anyway with DB_SET. + */ + if ( 0 == rc || ENOMEM == rc ) + rc = clcache_cursor_get ( cursor, buf, flag ); + + } + + /* + * Don't keep a cursor open across the whole replication session. + * That had caused noticable DB resource contention. + */ + if ( cursor ) { + cursor->c_close ( cursor ); + } + + if ( txn ) { + txn->commit ( txn, DB_TXN_NOSYNC ); + } + + PR_Unlock ( buf->buf_busy_list->bl_lock ); + + buf->buf_record_ptr = NULL; + if ( 0 == rc ) { + DB_MULTIPLE_INIT ( buf->buf_record_ptr, &buf->buf_data ); + if ( NULL == buf->buf_record_ptr ) + rc = DB_NOTFOUND; + else + buf->buf_load_cnt++; + } + + return rc; +} + +/* + * Gets the next change from the buffer. + * *key : output - key of the next change, or NULL if no more change + * *data: output - data of the next change, or NULL if no more change + */ +int +clcache_get_next_change ( CLC_Buffer *buf, void **key, size_t *keylen, void **data, size_t *datalen, CSN **csn ) +{ + int skip = 1; + int rc = 0; + + do { + *key = *data = NULL; + *keylen = *datalen = 0; + + if ( buf->buf_record_ptr ) { + DB_MULTIPLE_KEY_NEXT ( buf->buf_record_ptr, &buf->buf_data, + *key, *keylen, *data, *datalen ); + } + + /* + * We're done with the current buffer. Now load the next chunk. + */ + if ( NULL == *key && CLC_STATE_READY == buf->buf_state ) { + rc = clcache_load_buffer ( buf, NULL, DB_NEXT ); + if ( 0 == rc && buf->buf_record_ptr ) { + DB_MULTIPLE_KEY_NEXT ( buf->buf_record_ptr, &buf->buf_data, + *key, *keylen, *data, *datalen ); + } + } + + /* Compare the new change to the local and remote RUVs */ + if ( NULL != *key ) { + buf->buf_record_cnt++; + csn_init_by_string ( buf->buf_current_csn, (char*)*key ); + skip = clcache_skip_change ( buf ); + if (skip) buf->buf_record_skipped++; + } + } + while ( rc == 0 && *key && skip ); + + if ( NULL == *key ) { + *key = NULL; + *csn = NULL; + rc = DB_NOTFOUND; + } + else { + *csn = buf->buf_current_csn; + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "load=%d rec=%d csn=%s\n", + buf->buf_load_cnt, buf->buf_record_cnt, (char*)*key ); + } + + return rc; +} + +static void +clcache_refresh_consumer_maxcsns ( CLC_Buffer *buf ) +{ + int i; + + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + ruv_get_largest_csn_for_replica ( + buf->buf_consumer_ruv, + buf->buf_cscbs[i]->rid, + &buf->buf_cscbs[i]->consumer_maxcsn ); + } +} + +static int +clcache_refresh_local_maxcsn ( const ruv_enum_data *rid_data, void *data ) +{ + CLC_Buffer *buf = (CLC_Buffer*) data; + ReplicaId rid; + int rc = 0; + int i; + + rid = csn_get_replicaid ( rid_data->csn ); + + /* + * No need to create cscb for consumer's RID. + * If RID==65535, the CSN is originated from a + * legacy consumer. In this case the supplier + * and the consumer may have the same RID. + */ + if ( rid == buf->buf_consumer_rid && rid != MAX_REPLICA_ID ) + return rc; + + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + if ( buf->buf_cscbs[i]->rid == rid ) + break; + } + if ( i >= buf->buf_num_cscbs ) { + buf->buf_cscbs[i] = clcache_new_cscb (); + if ( buf->buf_cscbs[i] == NULL ) { + return -1; + } + buf->buf_cscbs[i]->rid = rid; + buf->buf_num_cscbs++; + } + + csn_dup_or_init_by_csn ( &buf->buf_cscbs[i]->local_maxcsn, rid_data->csn ); + + if ( buf->buf_cscbs[i]->consumer_maxcsn && + csn_compare (buf->buf_cscbs[i]->consumer_maxcsn, rid_data->csn) >= 0 ) { + /* No change need to be sent for this RID */ + buf->buf_cscbs[i]->state = CLC_STATE_UP_TO_DATE; + } + + return rc; +} + +static int +clcache_refresh_local_maxcsns ( CLC_Buffer *buf ) +{ + int i; + + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + csn_dup_or_init_by_csn ( &buf->buf_cscbs[i]->prev_local_maxcsn, + buf->buf_cscbs[i]->local_maxcsn ); + } + return ruv_enumerate_elements ( buf->buf_local_ruv, clcache_refresh_local_maxcsn, buf ); +} + +/* + * Algorithm: + * + * 1. Snapshot local RUVs; + * 2. Load buffer; + * 3. Send to the consumer only those CSNs that are covered + * by the RUVs snapshot taken in the first step; + * All CSNs that are covered by the RUVs snapshot taken in the + * first step are guaranteed in consecutive order for the respected + * RIDs because of the the CSN pending list control; + * A CSN that is not covered by the RUVs snapshot may be out of order + * since it is possible that a smaller CSN might not have committed + * yet by the time the buffer was loaded. + * 4. Determine anchorcsn for each RID: + * + * Case| Local vs. Buffer | New Local | Next + * | MaxCSN MaxCSN | MaxCSN | Anchor-CSN + * ----+-------------------+-----------+---------------- + * 1 | Cl >= Cb | * | Cb + * 2 | Cl < Cb | Cl | Cb + * 3 | Cl < Cb | Cl2 | Cl + * + * 5. Determine anchorcsn for next load: + * Anchor-CSN = min { all Next-Anchor-CSN, Buffer-MaxCSN } + */ +static int +clcache_adjust_anchorcsn ( CLC_Buffer *buf ) +{ + PRBool hasChange = PR_FALSE; + struct csn_seq_ctrl_block *cscb; + int rc = 0; + int i; + + if ( buf->buf_state == CLC_STATE_READY ) { + for ( i = 0; i < buf->buf_num_cscbs; i++ ) { + cscb = buf->buf_cscbs[i]; + + if ( cscb->state == CLC_STATE_UP_TO_DATE ) + continue; + + /* + * Case 3 unsafe ruv change: next buffer load should start + * from where the maxcsn in the old ruv was. Since each + * cscb has remembered the maxcsn sent to the consumer, + * CSNs that may be loaded again could easily be skipped. + */ + if ( cscb->prev_local_maxcsn && + csn_compare (cscb->prev_local_maxcsn, buf->buf_current_csn) < 0 && + csn_compare (cscb->local_maxcsn, cscb->prev_local_maxcsn) != 0 ) { + hasChange = PR_TRUE; + cscb->state = CLC_STATE_READY; + csn_init_by_csn ( buf->buf_current_csn, cscb->prev_local_maxcsn ); + csn_as_string ( cscb->prev_local_maxcsn, 0, (char*)buf->buf_key.data ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "adjust anchor csn upon %s\n", + ( cscb->state == CLC_STATE_CSN_GT_RUV ? "out of sequence csn" : "unsafe ruv change") ); + continue; + } + + /* + * check if there are still changes to send for this RID + * Assume we had compared the local maxcsn and the consumer + * max csn before this function was called and hence the + * cscb->state had been set accordingly. + */ + if ( hasChange == PR_FALSE && + csn_compare (cscb->local_maxcsn, buf->buf_current_csn) > 0 ) { + hasChange = PR_TRUE; + } + } + } + + if ( !hasChange ) { + buf->buf_state = CLC_STATE_DONE; + } + + return buf->buf_state; +} + +static int +clcache_skip_change ( CLC_Buffer *buf ) +{ + struct csn_seq_ctrl_block *cscb = NULL; + ReplicaId rid; + int skip = 1; + int i; + + do { + + rid = csn_get_replicaid ( buf->buf_current_csn ); + + /* + * Skip CSN that is originated from the consumer. + * If RID==65535, the CSN is originated from a + * legacy consumer. In this case the supplier + * and the consumer may have the same RID. + */ + if (rid == buf->buf_consumer_rid && rid != MAX_REPLICA_ID) + break; + + /* Skip helper entry (ENTRY_COUNT, PURGE_RUV and so on) */ + if ( cl5HelperEntry ( NULL, buf->buf_current_csn ) == PR_TRUE ) { + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "Skip helper entry type=%d\n", csn_get_time( buf->buf_current_csn )); + break; + } + + /* Find csn sequence control block for the current rid */ + for (i = 0; i < buf->buf_num_cscbs && buf->buf_cscbs[i]->rid != rid; i++); + + /* Skip CSN whose RID is unknown to the local RUV snapshot */ + if ( i >= buf->buf_num_cscbs ) { + buf->buf_state = CLC_STATE_NEW_RID; + break; + } + + cscb = buf->buf_cscbs[i]; + + /* Skip if the consumer is already up-to-date for the RID */ + if ( cscb->state == CLC_STATE_UP_TO_DATE ) { + break; + } + + /* Skip CSN whose preceedents are not covered by local RUV snapshot */ + if ( cscb->state == CLC_STATE_CSN_GT_RUV ) { + break; + } + + /* Skip CSNs already covered by consumer RUV */ + if ( cscb->consumer_maxcsn && + csn_compare ( buf->buf_current_csn, cscb->consumer_maxcsn ) <= 0 ) { + break; + } + + /* Send CSNs that are covered by the local RUV snapshot */ + if ( csn_compare ( buf->buf_current_csn, cscb->local_maxcsn ) <= 0 ) { + skip = 0; + csn_dup_or_init_by_csn ( &cscb->consumer_maxcsn, buf->buf_current_csn ); + break; + } + + /* + * Promote the local maxcsn to its next neighbor + * to keep the current session going. Skip if we + * are not sure if current_csn is the neighbor. + */ + if ( csn_time_difference(buf->buf_current_csn, cscb->local_maxcsn) == 0 && + (csn_get_seqnum(buf->buf_current_csn) == + csn_get_seqnum(cscb->local_maxcsn) + 1) ) { + csn_init_by_csn ( cscb->local_maxcsn, buf->buf_current_csn ); + csn_init_by_csn ( cscb->consumer_maxcsn, buf->buf_current_csn ); + skip = 0; + break; + } + + /* Skip CSNs not covered by local RUV snapshot */ + cscb->state = CLC_STATE_CSN_GT_RUV; + + } while (0); + +#ifdef DEBUG + if (skip && cscb) { + char consumer[24] = {'\0'}; + char local[24] = {'\0'}; + char current[24] = {'\0'}; + + if ( cscb->consumer_maxcsn ) + csn_as_string ( cscb->consumer_maxcsn, PR_FALSE, consumer ); + if ( cscb->local_maxcsn ) + csn_as_string ( cscb->local_maxcsn, PR_FALSE, local ); + csn_as_string ( buf->buf_current_csn, PR_FALSE, current ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "Skip %s consumer=%s local=%s\n", current, consumer, local ); + } +#endif + + return skip; +} + +static struct csn_seq_ctrl_block * +clcache_new_cscb () +{ + struct csn_seq_ctrl_block *cscb; + + cscb = (struct csn_seq_ctrl_block *) slapi_ch_calloc ( 1, sizeof (struct csn_seq_ctrl_block) ); + if (cscb == NULL) { + slapi_log_error ( SLAPI_LOG_FATAL, NULL, "clcache: malloc failure\n" ); + } + return cscb; +} + +static void +clcache_free_cscb ( struct csn_seq_ctrl_block ** cscb ) +{ + csn_free ( & (*cscb)->consumer_maxcsn ); + csn_free ( & (*cscb)->local_maxcsn ); + csn_free ( & (*cscb)->prev_local_maxcsn ); + slapi_ch_free ( (void **) cscb ); +} + +/* + * Allocate and initialize a new buffer + * It is called when there is a request for a buffer while + * buffer free list is empty. + */ +static CLC_Buffer * +clcache_new_buffer ( ReplicaId consumer_rid ) +{ + CLC_Buffer *buf = NULL; + int page_count = 0; + int welldone = 0; + int rc = 0; + + do { + + buf = (CLC_Buffer*) slapi_ch_calloc (1, sizeof(CLC_Buffer)); + if ( NULL == buf ) + break; + + buf->buf_key.flags = DB_DBT_USERMEM; + buf->buf_key.ulen = CSN_STRSIZE + 1; + buf->buf_key.size = CSN_STRSIZE; + buf->buf_key.data = slapi_ch_calloc( 1, buf->buf_key.ulen ); + if ( NULL == buf->buf_key.data ) + break; + + buf->buf_data.flags = DB_DBT_USERMEM; + buf->buf_data.ulen = _pool->pl_buffer_default_pages * DEFAULT_CLC_BUFFER_PAGE_SIZE; + buf->buf_data.data = slapi_ch_malloc( buf->buf_data.ulen ); + if ( NULL == buf->buf_data.data ) + break; + + if ( NULL == ( buf->buf_current_csn = csn_new()) ) + break; + + buf->buf_state = CLC_STATE_READY; + buf->buf_agmt_name = get_thread_private_agmtname(); + buf->buf_consumer_rid = consumer_rid; + buf->buf_num_cscbs = 0; + + welldone = 1; + + } while (0); + + if ( !welldone ) { + clcache_delete_buffer ( &buf ); + } + + return buf; +} + +/* + * Deallocates a buffer. + * It is called when a buffer is returned to the buffer pool + * and the pool size is over the limit. + */ +static void +clcache_delete_buffer ( CLC_Buffer **buf ) +{ + if ( buf && *buf ) { + slapi_ch_free (&( (*buf)->buf_key.data )); + slapi_ch_free (&( (*buf)->buf_data.data )); + csn_free (&( (*buf)->buf_current_csn )); + csn_free (&( (*buf)->buf_missing_csn )); + slapi_ch_free ( (void **) buf ); + } +} + +static CLC_Busy_List * +clcache_new_busy_list () +{ + CLC_Busy_List *bl; + int welldone = 0; + + do { + if ( NULL == (bl = ( CLC_Busy_List* ) slapi_ch_calloc (1, sizeof(CLC_Busy_List)) )) + break; + + if ( NULL == (bl->bl_lock = PR_NewLock ()) ) + break; + + /* + if ( NULL == (bl->bl_max_csn = csn_new ()) ) + break; + */ + + welldone = 1; + } + while (0); + + if ( !welldone ) { + clcache_delete_busy_list ( &bl ); + } + + return bl; +} + +static void +clcache_delete_busy_list ( CLC_Busy_List **bl ) +{ + if ( bl && *bl ) { + if ( (*bl)->bl_lock ) { + PR_DestroyLock ( (*bl)->bl_lock ); + } + /* csn_free (&( (*bl)->bl_max_csn )); */ + slapi_ch_free ( (void **) bl ); + } +} + +static int +clcache_enqueue_busy_list ( DB *db, CLC_Buffer *buf ) +{ + CLC_Busy_List *bl; + int rc = 0; + + PR_RWLock_Rlock ( _pool->pl_lock ); + for ( bl = _pool->pl_busy_lists; bl && bl->bl_db != db; bl = bl->bl_next ); + PR_RWLock_Unlock ( _pool->pl_lock ); + + if ( NULL == bl ) { + if ( NULL == ( bl = clcache_new_busy_list ()) ) { + rc = ENOMEM; + } + else { + PR_RWLock_Wlock ( _pool->pl_lock ); + bl->bl_db = db; + bl->bl_next = _pool->pl_busy_lists; + _pool->pl_busy_lists = bl; + PR_RWLock_Unlock ( _pool->pl_lock ); + } + } + + if ( NULL != bl ) { + PR_Lock ( bl->bl_lock ); + buf->buf_busy_list = bl; + buf->buf_next = bl->bl_buffers; + bl->bl_buffers = buf; + PR_Unlock ( bl->bl_lock ); + } + + return rc; +} + +static int +clcache_open_cursor ( DB_TXN *txn, CLC_Buffer *buf, DBC **cursor ) +{ + int rc; + + rc = buf->buf_busy_list->bl_db->cursor ( buf->buf_busy_list->bl_db, txn, cursor, 0 ); + if ( rc != 0 ) { + slapi_log_error ( SLAPI_LOG_FATAL, get_thread_private_agmtname(), + "clcache: failed to open cursor; db error - %d %s\n", + rc, db_strerror(rc)); + } + + return rc; +} + +static int +clcache_cursor_get ( DBC *cursor, CLC_Buffer *buf, int flag ) +{ + int rc; + + rc = cursor->c_get ( cursor, + & buf->buf_key, + & buf->buf_data, + buf->buf_load_flag | flag ); + if ( ENOMEM == rc ) { + /* + * The record takes more space than the current size of the + * buffer. Fortunately, buf->buf_data.size has been set by + * c_get() to the actual data size needed. So we can + * reallocate the data buffer and try to read again. + */ + buf->buf_data.ulen = ( buf->buf_data.size / DEFAULT_CLC_BUFFER_PAGE_SIZE + 1 ) * DEFAULT_CLC_BUFFER_PAGE_SIZE; + buf->buf_data.data = slapi_ch_realloc ( buf->buf_data.data, buf->buf_data.ulen ); + if ( buf->buf_data.data != NULL ) { + rc = cursor->c_get ( cursor, + &( buf->buf_key ), + &( buf->buf_data ), + buf->buf_load_flag | flag ); + slapi_log_error ( SLAPI_LOG_REPL, buf->buf_agmt_name, + "clcache: (%d | %d) %s reallocated and retry returns %d\n", buf->buf_load_flag, flag, buf->buf_key.data, rc ); + } + } + + switch ( rc ) { + case EINVAL: + slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name, + "clcache_cursor_get: invalid parameter\n" ); + break; + + case ENOMEM: + slapi_log_error ( SLAPI_LOG_FATAL, buf->buf_agmt_name, + "clcache_cursor_get: cann't allocate %u bytes\n", buf->buf_data.ulen ); + break; + + default: + break; + } + + return rc; +} + +static void +csn_dup_or_init_by_csn ( CSN **csn1, CSN *csn2 ) +{ + if ( *csn1 == NULL ) + *csn1 = csn_new(); + csn_init_by_csn ( *csn1, csn2 ); +} diff --git a/ldap/servers/plugins/replication/cl5_clcache.h b/ldap/servers/plugins/replication/cl5_clcache.h new file mode 100644 index 00000000..93024d1e --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_clcache.h @@ -0,0 +1,22 @@ +#ifndef CL5_CLCACHE_H +#define CL5_CLCACHE_H + +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "db.h" +#include "slapi-private.h" + +typedef struct clc_buffer CLC_Buffer; + +int clcache_init ( DB_ENV **dbenv ); +void clcache_set_config ( CL5DBConfig * config ); +int clcache_get_buffer ( CLC_Buffer **buf, DB *db, ReplicaId consumer_rid, const RUV *consumer_ruv, const RUV *local_ruv ); +int clcache_load_buffer ( CLC_Buffer *buf, CSN *startCSN, int flag ); +void clcache_return_buffer ( CLC_Buffer **buf ); +int clcache_get_next_change ( CLC_Buffer *buf, void **key, size_t *keylen, void **data, size_t *datalen, CSN **csn ); + +#endif diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c new file mode 100644 index 00000000..58c79dc1 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_config.c @@ -0,0 +1,868 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_config.c - functions to process changelog configuration + */ + +#include <string.h> +#include <prio.h> +#include "repl5.h" +#include "cl5.h" +#include "cl5_clcache.h" /* To configure the Changelog Cache */ +#include "intrinsics.h" /* JCMREPL - Is this bad? */ +#ifdef TEST_CL5 +#include "cl5_test.h" +#endif + +#define CONFIG_BASE "cn=changelog5,cn=config" /*"cn=changelog,cn=supplier,cn=replication5.0,cn=replication,cn=config"*/ +#define CONFIG_FILTER "(objectclass=*)" + +static PRRWLock *s_configLock; /* guarantees that only on thread at a time + modifies changelog configuration */ + +/* Forward Declartions */ +static int changelog5_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int changelog5_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg); + +static void changelog5_extract_config(Slapi_Entry* entry, changelog5Config *config); +static changelog5Config * changelog5_dup_config(changelog5Config *config); +static void replace_bslash (char *dir); +static int notify_replica (Replica *r, void *arg); +static int _is_absolutepath (char *dir); + +int changelog5_config_init() +{ + /* The FE DSE *must* be initialised before we get here */ + + /* create the configuration lock */ + s_configLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "config_lock"); + if (s_configLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_init: failed to create configurationlock; " + "NSPR error - %d\n",PR_GetError ()); + return 1; + } + + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_add, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_modify, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, dont_allow_that, NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_delete, NULL); + + return 0; +} + +void changelog5_config_cleanup() +{ + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_add); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_modify); + slapi_config_remove_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, dont_allow_that); + slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_BASE, + CONFIG_FILTER, changelog5_config_delete); + + if (s_configLock) + { + PR_DestroyRWLock (s_configLock); + s_configLock = NULL; + } +} + +int changelog5_read_config (changelog5Config *config) +{ + int rc = LDAP_SUCCESS; + Slapi_PBlock *pb; + + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, CONFIG_BASE, LDAP_SCOPE_BASE, CONFIG_FILTER, NULL, 0, NULL, + NULL, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + if ( LDAP_SUCCESS == rc ) + { + Slapi_Entry **entries = NULL; + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries ); + if ( NULL != entries && NULL != entries[0]) + { + /* Extract the config info from the changelog entry */ + changelog5_extract_config(entries[0], config); + } + } + else + { + memset (config, 0, sizeof (*config)); + rc = LDAP_SUCCESS; + } + + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return rc; +} + +void changelog5_config_done (changelog5Config *config) +{ + if (config) { + /* slapi_ch_free_string accepts NULL pointer */ + slapi_ch_free_string (&config->maxAge); + slapi_ch_free_string (&config->dir); + } +} + +void changelog5_config_free (changelog5Config **config) +{ + changelog5_config_done(*config); + slapi_ch_free((void **)config); +} + +static int +changelog5_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + int rc; + changelog5Config config; + + *returncode = LDAP_SUCCESS; + + PR_RWLock_Wlock (s_configLock); + + /* we already have a configured changelog - don't need to do anything + since add operation will fail */ + if (cl5GetState () == CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "attempt to add changelog when it already exists"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: changelog already exist; " + "request ignored\n"); + goto done; + } + + changelog5_extract_config(e, &config); + if (config.dir == NULL) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "NULL changelog directory"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: NULL changelog directory\n"); + goto done; + } + + /* start the changelog */ + rc = cl5Open (config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to start changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: failed to start changelog\n"); + goto done; + } + + /* set trimming parameters */ + rc = cl5ConfigTrimming (config.maxEntries, config.maxAge); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to configure changelog trimming; error - %d", rc); + } + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_add: failed to configure changelog trimming\n"); + goto done; + } + + /* notify all the replicas that the changelog is configured + so that the can log dummy changes if necessary. */ + replica_enumerate_replicas (notify_replica, NULL); + +#ifdef TEST_CL5 + testChangelog (TEST_ITERATION); +#endif + +done:; + PR_RWLock_Unlock (s_configLock); + changelog5_config_done (&config); + if (*returncode == LDAP_SUCCESS) + { + if (returntext) + { + returntext[0] = '\0'; + } + + return SLAPI_DSE_CALLBACK_OK; + } + + return SLAPI_DSE_CALLBACK_ERROR; +} + +static int +changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + int rc= 0; + LDAPMod **mods; + int i; + changelog5Config config; + changelog5Config * originalConfig = NULL; + char *currentDir = NULL; + + *returncode = LDAP_SUCCESS; + + /* changelog must be open before its parameters can be modified */ + if (cl5GetState() != CL5_STATE_OPEN) + { + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: changelog is not configured\n"); + return SLAPI_DSE_CALLBACK_ERROR; + } + + PR_RWLock_Wlock (s_configLock); + + /* changelog must be open before its parameters can be modified */ + if (cl5GetState() != CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: changelog is not configured\n"); + goto done; + } + + /* + * Extract all the original configuration: This is needed to ensure that the configuration + * is trully reloaded. This was not needed before 091401 because the changelog configuration + * was always hardcoded (NULL was being passed to cl5Open). Now we need to ensure we pass to + * cl5Open the proper configuration... + */ + changelog5_extract_config(e, &config); + originalConfig = changelog5_dup_config(&config); + + /* Reset all the attributes that have been potentially modified by the current MODIFY operation */ + slapi_ch_free_string(&config.dir); + config.dir = NULL; + config.maxEntries = CL5_NUM_IGNORE; + slapi_ch_free_string(&config.maxAge); + config.maxAge = slapi_ch_strdup(CL5_STR_IGNORE); + config.dbconfig.maxChCacheEntries = 0; + config.dbconfig.maxChCacheSize = CL5_NUM_IGNORE; + + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + for (i = 0; mods[i] != NULL; i++) + { + if (mods[i]->mod_op & LDAP_MOD_DELETE) + { + /* We don't support deleting changelog attributes */ + } + else + { + int j; + for (j = 0; ((mods[i]->mod_values[j]) && (LDAP_SUCCESS == rc)); j++) + { + char *config_attr, *config_attr_value; + config_attr = (char *) mods[i]->mod_type; + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + +#define ATTR_MODIFIERSNAME "modifiersname" +#define ATTR_MODIFYTIMESTAMP "modifytimestamp" + + if ( strcasecmp ( config_attr, ATTR_MODIFIERSNAME ) == 0 ) { + continue; + } + if ( strcasecmp ( config_attr, ATTR_MODIFYTIMESTAMP ) == 0 ) { + continue; + } + /* replace existing value */ + if ( strcasecmp (config_attr, CONFIG_CHANGELOG_DIR_ATTRIBUTE ) == 0 ) + { + if (config_attr_value && config_attr_value[0] != '\0') + { + slapi_ch_free_string(&config.dir); + config.dir = slapi_ch_strdup(config_attr_value); + replace_bslash (config.dir); + } + else + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "null changelog directory"); + } + goto done; + } + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE ) == 0 ) + { + if (config_attr_value && config_attr_value[0] != '\0') + { + config.maxEntries = atoi (config_attr_value); + } + else + { + config.maxEntries = 0; + } + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE ) == 0 ) + { + slapi_ch_free_string(&config.maxAge); + config.maxAge = slapi_ch_strdup(config_attr_value); + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_CACHESIZE ) == 0 ) + { /* The Changelog Cache Size parameters can be modified online without a need for restart */ + if (config_attr_value && config_attr_value[0] != '\0') + { + config.dbconfig.maxChCacheEntries = atoi (config_attr_value); + } + else + { + config.dbconfig.maxChCacheEntries = 0; + } + } + else if ( strcasecmp ( config_attr, CONFIG_CHANGELOG_CACHEMEMSIZE ) == 0 ) + { /* The Changelog Cache Size parameters can be modified online without a need for restart */ + if (config_attr_value && config_attr_value[0] != '\0') + { + config.dbconfig.maxChCacheSize = atoi (config_attr_value); + } + else + { + config.dbconfig.maxChCacheSize = 0; + } + } + else + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + if (returntext) + { + sprintf (returntext, "Unwilling to apply %s mods while the server is running", config_attr); + } + goto done; + } + } + } + } + /* Undo the reset above for all the modifiable attributes that were not modified + * except config.dir */ + if (config.maxEntries == CL5_NUM_IGNORE) + config.maxEntries = originalConfig->maxEntries; + if (strcmp (config.maxAge, CL5_STR_IGNORE) == 0) { + slapi_ch_free_string(&config.maxAge); + if (originalConfig->maxAge) + config.maxAge = slapi_ch_strdup(originalConfig->maxAge); + } + if (config.dbconfig.maxChCacheEntries == 0) + config.dbconfig.maxChCacheEntries = originalConfig->dbconfig.maxChCacheEntries; + if (config.dbconfig.maxChCacheSize == CL5_NUM_IGNORE) + config.dbconfig.maxChCacheSize = originalConfig->dbconfig.maxChCacheSize; + + + /* attempt to change chagelog dir */ + if (config.dir) + { + currentDir = cl5GetDir (); + if (currentDir == NULL) + { + /* something is wrong: we should never be here */ + *returncode = 1; + if (returntext) + { + strcpy (returntext, "internal failure"); + } + + goto done; + } + +#ifdef _WIN32 + if (strcasecmp (currentDir, config.dir) != 0) +#else /* On Unix, path are case sensitive */ + if (strcmp (currentDir, config.dir) != 0) +#endif + { + if (!_is_absolutepath(config.dir) || (CL5_SUCCESS != cl5CreateDirIfNeeded(config.dir))) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "invalid changelog directory or insufficient access"); + } + + goto done; + } + + /* changelog directory changed - need to remove the + previous changelog and create new one */ + + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, + "changelog5_config_modify: changelog directory changed; " + "old dir - %s, new dir - %s; recreating changelog.\n", + currentDir, config.dir); + + /* this call will block until all threads using changelog + release changelog by calling cl5RemoveThread () */ + rc = cl5Close (); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to close changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to close changelog\n"); + goto done; + } + + rc = cl5Delete (currentDir); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to remove changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to remove changelog\n"); + goto done; + } + + rc = cl5Open (config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to restart changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to restart changelog\n"); + /* before finishing, let's try to do some error recovery */ + if (CL5_SUCCESS != cl5Open(currentDir, &config.dbconfig)) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to restore previous changelog\n"); + } + goto done; + } + } + } + + /* one of the changelog parameters is modified */ + if (config.maxEntries != CL5_NUM_IGNORE || + strcmp (config.maxAge, CL5_STR_IGNORE) != 0) + { + rc = cl5ConfigTrimming (config.maxEntries, config.maxAge); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to configure changelog trimming; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_modify: failed to configure changelog trimming\n"); + goto done; + } + } + + if (config.dbconfig.maxChCacheEntries != 0 || config.dbconfig.maxChCacheSize != CL5_NUM_IGNORE) + clcache_set_config(&config.dbconfig); + +done:; + PR_RWLock_Unlock (s_configLock); + + changelog5_config_done (&config); + changelog5_config_free (&originalConfig); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)¤tDir); + + if (*returncode == LDAP_SUCCESS) + { + + if (returntext) + { + returntext[0] = '\0'; + } + + return SLAPI_DSE_CALLBACK_OK; + } + + return SLAPI_DSE_CALLBACK_ERROR; +} + +static int +changelog5_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + int rc; + char *currentDir = NULL; + *returncode = LDAP_SUCCESS; + + /* changelog must be open before it can be deleted */ + if (cl5GetState () != CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: chagelog is not configured\n"); + return SLAPI_DSE_CALLBACK_ERROR; + } + + PR_RWLock_Wlock (s_configLock); + + /* changelog must be open before it can be deleted */ + if (cl5GetState () != CL5_STATE_OPEN) + { + *returncode = 1; + if (returntext) + { + strcpy (returntext, "changelog is not configured"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: chagelog is not configured\n"); + goto done; + } + + currentDir = cl5GetDir (); + + if (currentDir == NULL) + { + /* something is wrong: we should never be here */ + *returncode = 1; + if (returntext) + { + strcpy (returntext, "internal failure"); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: NULL directory\n"); + goto done; + } + + /* this call will block until all threads using changelog + release changelog by calling cl5RemoveThread () */ + rc = cl5Close (); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to close changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: failed to close changelog\n"); + goto done; + } + + rc = cl5Delete (currentDir); + if (rc != CL5_SUCCESS) + { + *returncode = 1; + if (returntext) + { + sprintf (returntext, "failed to remove changelog; error - %d", rc); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_config_delete: failed to remove changelog\n"); + goto done; + } + +done:; + PR_RWLock_Unlock (s_configLock); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)¤tDir); + + if (*returncode == LDAP_SUCCESS) + { + if (returntext) + { + returntext[0] = '\0'; + } + + return SLAPI_DSE_CALLBACK_OK; + } + + return SLAPI_DSE_CALLBACK_ERROR; +} + +static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +static changelog5Config * changelog5_dup_config(changelog5Config *config) +{ + changelog5Config *dup = (changelog5Config *) slapi_ch_calloc(1, sizeof(changelog5Config)); + + if (config->dir) + dup->dir = slapi_ch_strdup(config->dir); + if (config->maxAge) + dup->maxAge = slapi_ch_strdup(config->maxAge); + + dup->maxEntries = config->maxEntries; + + /*memcpy((void *) &dup->dbconfig, (const void *) &config->dbconfig, sizeof(CL5DBConfig));*/ + dup->dbconfig.cacheSize = config->dbconfig.cacheSize; + dup->dbconfig.durableTrans = config->dbconfig.durableTrans; + dup->dbconfig.checkpointInterval = config->dbconfig.checkpointInterval; + dup->dbconfig.circularLogging = config->dbconfig.circularLogging; + dup->dbconfig.pageSize = config->dbconfig.pageSize; + dup->dbconfig.logfileSize = config->dbconfig.logfileSize; + dup->dbconfig.maxTxnSize = config->dbconfig.maxTxnSize; + dup->dbconfig.fileMode = config->dbconfig.fileMode; + dup->dbconfig.verbose = config->dbconfig.verbose; + dup->dbconfig.debug = config->dbconfig.debug; + dup->dbconfig.tricklePercentage = config->dbconfig.tricklePercentage; + dup->dbconfig.spinCount = config->dbconfig.spinCount; + dup->dbconfig.maxChCacheEntries = config->dbconfig.maxChCacheEntries; + dup->dbconfig.maxChCacheSize = config->dbconfig.maxChCacheSize; + dup->dbconfig.nb_lock_config = config->dbconfig.nb_lock_config; + + return dup; +} + + +/* + * Given the changelog configuration entry, extract the configuration directives. + */ +static void changelog5_extract_config(Slapi_Entry* entry, changelog5Config *config) +{ + char *arg; + + memset (config, 0, sizeof (*config)); + config->dir = slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_DIR_ATTRIBUTE); + replace_bslash (config->dir); + + arg= slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE); + if (arg) + { + config->maxEntries = atoi (arg); + slapi_ch_free_string(&arg); + } + + config->maxAge = slapi_entry_attr_get_charptr(entry,CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE); + + /* + * Read the Changelog Internal Configuration Parameters for the Changelog DB + * (db cache size, db settings...) + */ + + /* Set configuration default values first... */ + config->dbconfig.cacheSize = CL5_DEFAULT_CONFIG_DB_DBCACHESIZE; + config->dbconfig.durableTrans = CL5_DEFAULT_CONFIG_DB_DURABLE_TRANSACTIONS; + config->dbconfig.checkpointInterval = CL5_DEFAULT_CONFIG_DB_CHECKPOINT_INTERVAL; + config->dbconfig.circularLogging = CL5_DEFAULT_CONFIG_DB_CIRCULAR_LOGGING; + config->dbconfig.pageSize = CL5_DEFAULT_CONFIG_DB_PAGE_SIZE; + config->dbconfig.logfileSize = CL5_DEFAULT_CONFIG_DB_LOGFILE_SIZE; + config->dbconfig.maxTxnSize = CL5_DEFAULT_CONFIG_DB_TXN_MAX; + config->dbconfig.verbose = CL5_DEFAULT_CONFIG_DB_VERBOSE; + config->dbconfig.debug = CL5_DEFAULT_CONFIG_DB_DEBUG; + config->dbconfig.tricklePercentage = CL5_DEFAULT_CONFIG_DB_TRICKLE_PERCENTAGE; + config->dbconfig.spinCount = CL5_DEFAULT_CONFIG_DB_SPINCOUNT; + config->dbconfig.nb_lock_config = CL5_DEFAULT_CONFIG_NB_LOCK; + + /* Now read from the entry to override default values if needed */ + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DBCACHESIZE); + if (arg) + { + size_t theSize = atoi (arg); + if (theSize > CL5_MIN_DB_DBCACHESIZE) + config->dbconfig.cacheSize = theSize; + else { + config->dbconfig.cacheSize = CL5_MIN_DB_DBCACHESIZE; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Warning: Changelog dbcache size too small. " + "Increasing the Memory Size to %d bytes\n", + CL5_MIN_DB_DBCACHESIZE); + } + slapi_ch_free_string(&arg); + } + + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DURABLE_TRANSACTIONS); + if (arg) + { + config->dbconfig.durableTrans = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_CHECKPOINT_INTERVAL); + if (arg) + { + config->dbconfig.checkpointInterval = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_CIRCULAR_LOGGING); + if (arg) + { + config->dbconfig.circularLogging = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_PAGE_SIZE); + if (arg) + { + config->dbconfig.pageSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_LOGFILE_SIZE); + if (arg) + { + config->dbconfig.logfileSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_MAXTXN_SIZE); + if (arg) + { + config->dbconfig.maxTxnSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_VERBOSE); + if (arg) + { + config->dbconfig.verbose = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_DEBUG); + if (arg) + { + config->dbconfig.debug = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_TRICKLE_PERCENTAGE); + if (arg) + { + config->dbconfig.tricklePercentage = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DB_SPINCOUNT); + if (arg) + { + config->dbconfig.spinCount = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_MAX_CONCURRENT_WRITES); + if (arg) + { + config->dbconfig.maxConcurrentWrites = atoi (arg); + slapi_ch_free_string(&arg); + } + if ( config->dbconfig.maxConcurrentWrites <= 0 ) + { + config->dbconfig.maxConcurrentWrites = CL5_DEFAULT_CONFIG_MAX_CONCURRENT_WRITES; + } + + /* + * Read the Changelog Internal Configuration Parameters for the Changelog Cache + */ + + /* Set configuration default values first... */ + config->dbconfig.maxChCacheEntries = CL5_DEFAULT_CONFIG_CACHESIZE; + config->dbconfig.maxChCacheSize = CL5_DEFAULT_CONFIG_CACHEMEMSIZE; + + /* Now read from the entry to override default values if needed */ + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_CACHESIZE); + if (arg) + { + config->dbconfig.maxChCacheEntries = atoi (arg); + slapi_ch_free_string(&arg); + } + arg= slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_CACHEMEMSIZE); + if (arg) + { + config->dbconfig.maxChCacheSize = atoi (arg); + slapi_ch_free_string(&arg); + } + arg = slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_NB_LOCK); + if (arg) + { + size_t theSize = atoi(arg); + if (theSize < CL5_MIN_NB_LOCK) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Warning: Changelog %s value is too low (%d). Set to minimal value instead (%d)\n", + CONFIG_CHANGELOG_NB_LOCK, theSize, CL5_MIN_NB_LOCK); + config->dbconfig.nb_lock_config = CL5_MIN_NB_LOCK; + } + else + { + config->dbconfig.nb_lock_config = theSize; + } + slapi_ch_free_string(&arg); + } + + clcache_set_config(&config->dbconfig); +} + +static void replace_bslash (char *dir) +{ + char *bslash; + + if (dir == NULL) + return; + + bslash = strchr (dir, '\\'); + while (bslash) + { + *bslash = '/'; + bslash = strchr (bslash, '\\'); + } +} + +static int notify_replica (Replica *r, void *arg) +{ + return replica_log_ruv_elements (r); +} + +static int _is_absolutepath (char * dir) +{ + if (dir[0] == '/') + return 1; +#if defined(_WIN32) + if (dir[2] == '/' && dir[1] == ':') + return 1; +#endif + return 0; +} diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c new file mode 100644 index 00000000..435299c0 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_init.c @@ -0,0 +1,77 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* cl5_init.c - implments initialization/cleanup functions for + 4.0 style changelog + */ + +#include "slapi-plugin.h" +#include "cl5.h" +#include "repl5.h" + +/* initializes changelog*/ +int changelog5_init() +{ + int rc; + changelog5Config config; + + rc = cl5Init (); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_init: failed to initialize changelog\n"); + return 1; + } + + /* read changelog configuration */ + changelog5_config_init (); + changelog5_read_config (&config); + + if (config.dir == NULL) + { + /* changelog is not configured - bail out */ + rc = 0; /* OK */ + goto done; + } + + /* start changelog */ + rc = cl5Open (config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_init: failed to start changelog at %s\n", + config.dir); + rc = 1; + goto done; + } + + /* set trimming parameters */ + rc = cl5ConfigTrimming (config.maxEntries, config.maxAge); + if (rc != CL5_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "changelog5_init: failed to configure changelog trimming\n"); + rc = 1; + goto done; + } + + rc = 0; + +done: + changelog5_config_done (&config); + return rc; +} + +/* cleanups changelog data */ +void changelog5_cleanup() +{ + /* close changelog */ + cl5Close (); + cl5Cleanup (); + + /* cleanup config */ + changelog5_config_cleanup (); +} diff --git a/ldap/servers/plugins/replication/cl5_test.c b/ldap/servers/plugins/replication/cl5_test.c new file mode 100644 index 00000000..b64a60f5 --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_test.c @@ -0,0 +1,830 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_test.c - changelog test cases */ +#include "cl5_test.h" +#include "slapi-plugin.h" +#include "cl5.h" + +#define REPLICA_ROOT "dc=example,dc=com" /* replica root */ +#define OP_COUNT 4 /* number of ops generated at a time */ +#define MOD_COUNT 5 +#define VALUE_COUNT 5 +#define ENTRY_COUNT 50 +#define CL_DN "cn=changelog5,cn=config" +#define INSTANCE_ATTR "nsslapd-instancedir" +#define REPLICA_OC "nsds5Replica" +#define REPLICA_RDN "cn=replica" + +static void testBasic (); +static void testBackupRestore (); +static void testIteration (); +static void testTrimming (); +static void testPerformance (); +static void testPerformanceMT (); +static void testLDIF (); +static void testAll (); +static int configureChangelog (); +static int configureReplica (); +static int populateChangelogOp (); +static int populateChangelog (int entryCount, CSN ***csnList); +static int processEntries (int entryCount, CSN **csnList); +static void clearCSNList (CSN ***csnList, int count); +static void threadMain (void *data); +static char* getBaseDir (const char *dir); +static LDAPMod **buildMods (); + +void testChangelog (TestType type) +{ + switch (type) + { + case TEST_BASIC: testBasic (); + break; + case TEST_BACKUP_RESTORE: testBackupRestore (); + break; + case TEST_ITERATION: testIteration (); + break; + case TEST_TRIMMING: testTrimming (); + break; + case TEST_PERFORMANCE: testPerformance (); + break; + case TEST_PERFORMANCE_MT: testPerformanceMT (); + break; + case TEST_LDIF: testLDIF (); + break; + case TEST_ALL: testAll (); + break; + default: printf ("Taste case %d is not supported\n", type); + } +} + +/* tests Open/Close, normal recovery, read/write/remove + of an entry */ +static void testBasic () +{ + int rc = 0; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting basic test ...\n"); + + /* ONREPL - we can't run the tests from the startup code because + operations can't be issued until all plugins are started. So, + instead, we do it when changelog is created + rc = configureChangelog (); */ + if (rc == 0) + { + rc = configureReplica (); + if (rc == 0) + { + rc = populateChangelogOp (); + } + } + + if (rc == 0) + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Basic test completed successfully\n"); + else + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Basic test failed\n"); +} + +static void testBackupRestore () +{ + char *dir; + int rc = -1; + char *baseDir; + char bkDir [MAXPATHLEN + 1]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting backup and recovery test ...\n"); + + dir = cl5GetDir (); + + if (dir) + { + baseDir = getBaseDir (dir); + sprintf (bkDir, "%s/clbackup", baseDir); + slapi_ch_free ((void**)&baseDir); + rc = cl5Backup (bkDir, NULL); + + if (rc == CL5_SUCCESS) + { + cl5Close (); + rc = cl5Restore (dir, bkDir, NULL); + if (rc == CL5_SUCCESS) + rc = cl5Open (dir, NULL); + + /* PR_RmDir (bkDir);*/ + } + } + + if (rc == CL5_SUCCESS) + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Backup and Restore test completed successfully\n"); + else + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Backup and Restore test failed\n"); +} + +static void testIteration () +{ + Object *r_obj; + Slapi_DN *r_root; + Replica *r; + char *replGen; + RUV *ruv; + CL5ReplayIterator *it = NULL; + slapi_operation_parameters op; + int rc; + int i; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting iteration test ...\n"); + + /* get replica object */ + r_root = slapi_sdn_new_dn_byval(REPLICA_ROOT); + r_obj = replica_get_replica_from_dn (r_root); + slapi_sdn_free (&r_root); + if (r_obj == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "replica is not configured for (%s)\n", + REPLICA_ROOT); + return; + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting first iteration pass ...\n"); + + /* configure empty consumer ruv */ + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + replGen = replica_get_generation (r); + ruv_init_new (replGen, 0, NULL, &ruv); + + /* create replay iterator */ + rc = cl5CreateReplayIterator (r_obj, ruv, &it); + if (it) + { + i = 0; + while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS) + { + ruv_set_csns (ruv, op.csn, NULL); + operation_parameters_done (&op); + i ++; + } + } + + if (it) + cl5DestroyReplayIterator (&it); + + if (rc == CL5_NOTFOUND) + { + if (i == 0) /* success */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass completed " + "successfully: no changes to replay\n"); + else /* incorrect number of entries traversed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass failed: " + "traversed %d entries; expected none\n", i); + } + else /* general error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "First iteration pass failed\n"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting second iteration pass ...\n"); + + /* add some entries */ + populateChangelogOp (); + + /* create replay iterator */ + rc = cl5CreateReplayIterator (r_obj, ruv, &it); + if (it) + { + i = 0; + while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS) + { + ruv_set_csns (ruv, op.csn, NULL); + operation_parameters_done (&op); + i ++; + } + } + + if (it) + cl5DestroyReplayIterator (&it); + + if (rc == CL5_NOTFOUND) + { + if (i == OP_COUNT) /* success */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass completed " + "successfully: %d entries traversed\n", i); + else /* incorrect number of entries traversed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed: " + "traversed %d entries; expected %d\n", i, OP_COUNT); + } + else /* general error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed\n"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting third iteration pass ...\n"); + /* add more entries */ + populateChangelogOp (); + + /* create replay iterator */ + rc = cl5CreateReplayIterator (r_obj, ruv, &it); + if (it) + { + i = 0; + while ((rc = cl5GetNextOperationToReplay (it, &op)) == CL5_SUCCESS) + { + ruv_set_csns (ruv, op.csn, NULL); + operation_parameters_done (&op); + i ++; + } + } + + if (it) + cl5DestroyReplayIterator (&it); + + if (rc == CL5_NOTFOUND) + { + if (i == OP_COUNT) /* success */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Third iteration pass completed " + "successfully: %d entries traversed\n", i); + else /* incorrect number of entries traversed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Third iteration pass failed: " + "traversed %d entries; expected %d\n", i, OP_COUNT); + } + else /* general error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Second iteration pass failed\n"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Iteration test is complete\n"); + + ruv_destroy (&ruv); + object_release (r_obj); + slapi_ch_free ((void**)&replGen); +} + +static void testTrimming () +{ + PRIntervalTime interval; + int count; + int rc; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting trimming test ...\n"); + + rc = populateChangelog (200, NULL); + + if (rc == 0) + { + interval = PR_SecondsToInterval(2); + DS_Sleep (interval); + + rc = populateChangelog (300, NULL); + + if (rc == 0) + rc = cl5ConfigTrimming (300, "1d"); + + interval = PR_SecondsToInterval(300); /* 5 min is default trimming interval */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Trimming test: sleeping for 5 minutes until trimming kicks in\n"); + DS_Sleep (interval); + + count = cl5GetOperationCount (NULL); + } + + if (rc == 0 && count == 300) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Trimming test completed successfully: changelog contains 300 entries\n"); + } + else if (rc == 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Trimming test failed: changelog contains %d entries; expected - 300\n", + count); + } + else /* general failure */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Trimming test failed\n"); +} + +static void testPerformance () +{ + PRTime starttime, endtime, totaltime; + int entryCount = 5000; + CSN **csnList = NULL; + int rc; + + starttime = PR_Now(); + + rc = populateChangelog (entryCount, &csnList); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Write performance:\n" + "entry count - %d, total time - %ldms\n" + "latency = %d msec\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryCount, entryCount * 1000 / totaltime); + + + starttime = endtime; + + rc = processEntries (entryCount, csnList); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Read performance:\n" + "entry count - %d, total time - %ld\n" + "latency = %d msec\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryCount, entryCount * 1000 / totaltime); + + clearCSNList (&csnList, entryCount); +} + +static int threadsLeft; +static void testPerformanceMT () +{ + PRTime starttime, endtime, totaltime; + int entryCount = 200; + int threadCount = 10; + int entryTotal; + int i; + PRIntervalTime interval; + + interval = PR_MillisecondsToInterval(100); + threadsLeft = threadCount * 2; + entryTotal = threadCount * entryCount; + starttime = PR_Now(); + + for (i = 0; i < threadCount; i++) + { + PR_CreateThread(PR_USER_THREAD, threadMain, (void*)&entryCount, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, 0); + } + + while (threadsLeft > 5) + DS_Sleep (interval); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Write performance:\n" + "entry count - %d, total time - %ld\n" + "latency = %d msec per entry\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryTotal, entryTotal * 1000 / totaltime); + + + starttime = endtime; + + while (threadsLeft != 0) + DS_Sleep (interval); + + endtime = PR_Now(); + + totaltime = (endtime - starttime) / 1000; /* ms */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Read performance:\n" + "entry count - %d, total time - %ld\n" + "latency = %d msec per entry\nthroughput = %d entry/sec\n", + entryCount, totaltime, + totaltime / entryTotal, entryTotal * 1000 / totaltime); +} + +static void testLDIF () +{ + char *clDir = cl5GetDir (); + int rc; + char *baseDir; + char ldifFile [MAXPATHLEN + 1]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "Starting LDIF test ...\n"); + + baseDir = getBaseDir (clDir); + sprintf (ldifFile, "%s/cl5.ldif", baseDir); + slapi_ch_free ((void**)&baseDir); + rc = populateChangelog (ENTRY_COUNT, NULL); + + if (rc == CL5_SUCCESS) + { + rc = cl5ExportLDIF (ldifFile, NULL); + if (rc == CL5_SUCCESS) + { + cl5Close(); + rc = cl5ImportLDIF (clDir, ldifFile, NULL); + if (rc == CL5_SUCCESS) + cl5Open(clDir, NULL); + } + } + + PR_Delete (ldifFile); + + if (rc == CL5_SUCCESS) + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "LDIF test completed successfully\n"); + else + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "LDIF test failed\n"); +} + +static void testAll () +{ + testBasic (); + + testIteration (); + + testBackupRestore (); + + testLDIF (); + + /* testTrimming ();*/ + +#if 0 + /* xxxPINAKI */ + /* these tests are not working correctly...the call to db->put() */ + /* just hangs forever */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Starting single threaded performance measurement ...\n"); + testPerformance (); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Starting multi threaded performance measurement ...\n"); + testPerformanceMT (); +#endif + +} + +static int populateChangelog (int entryCount, CSN ***csnList) +{ + CSN *csn; + int i; + slapi_operation_parameters op; + int rc; + char *uniqueid; + + if (csnList) + { + (*csnList) = (CSN**)slapi_ch_calloc (entryCount, sizeof (CSN*)); + } + + /* generate entries */ + for (i = 0; i < entryCount; i++) + { + /* ONREPL need to get replica object + rc = csnGetNewCSNForRepl (&csn); + if (rc != CL5_SUCCESS) */ + return -1; + + if (csnList) + (*csnList) [i] = csn_dup (csn); + memset (&op, 0, sizeof (op)); + op.csn = csn; + slapi_uniqueIDGenerateString(&uniqueid); + op.target_address.uniqueid = uniqueid; + op.target_address.dn = slapi_ch_strdup ("cn=entry,dc=example,dc=com"); + if (i % 5 == 0) + { + op.operation_type = SLAPI_OPERATION_MODRDN; + op.p.p_modrdn.modrdn_deloldrdn = 1; + op.p.p_modrdn.modrdn_newrdn = slapi_ch_strdup("cn=entry2,dc=example,dc=com"); + op.p.p_modrdn.modrdn_newsuperior_address.dn = NULL; + op.p.p_modrdn.modrdn_newsuperior_address.uniqueid = NULL; + op.p.p_modrdn.modrdn_mods = buildMods (); + } + else if (i % 4 == 0) + { + op.operation_type = SLAPI_OPERATION_DELETE; + } + else if (i % 3 == 0) + { + + op.operation_type = SLAPI_OPERATION_ADD; + op.p.p_add.target_entry = slapi_entry_alloc (); + slapi_entry_set_dn (op.p.p_add.target_entry, slapi_ch_strdup(op.target_address.dn)); + slapi_entry_set_uniqueid (op.p.p_add.target_entry, slapi_ch_strdup(op.target_address.uniqueid)); + slapi_entry_attr_set_charptr(op.p.p_add.target_entry, "objectclass", "top"); + slapi_entry_attr_set_charptr(op.p.p_add.target_entry, "cn", "entry"); + } + else + { + op.operation_type = SLAPI_OPERATION_MODIFY; + op.p.p_modify.modify_mods = buildMods (); + } + + /* ONREPL rc = cl5WriteOperation (&op, 1);*/ + operation_parameters_done (&op); + + if (rc != CL5_SUCCESS) + return -1; + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Successfully populated changelog with %d entries\n", entryCount); + return 0; +} + +static int processEntries (int entryCount, CSN **csnList) +{ + int i; + int rc = 0; + slapi_operation_parameters op; + + for (i = 0; i < entryCount; i++) + { + memset (&op, 0, sizeof (op)); + + op.csn = csn_dup (csnList [i]); + + /* rc = cl5GetOperation (&op);*/ + if (rc != CL5_SUCCESS) + return -1; + + operation_parameters_done (&op); + } + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, + "Successfully read %d entries from the changelog\n", entryCount); + return 0; +} + +void clearCSNList (CSN ***csnList, int count) +{ + int i; + + for (i = 0; i < count; i++) + { + csn_free (&((*csnList)[i])); + } + + slapi_ch_free ((void**)csnList); +} + +static void threadMain (void *data) +{ + int entryCount = *(int*)data; + CSN **csnList; + + populateChangelog (entryCount, &csnList); + PR_AtomicDecrement (&threadsLeft); + + processEntries (entryCount, csnList); + PR_AtomicDecrement (&threadsLeft); + + clearCSNList (&csnList, entryCount); +} + +static char* getBaseDir (const char *dir) +{ + char *baseDir = slapi_ch_strdup (dir); + char *ch; + + ch = &(baseDir [strlen (dir) - 2]); + + while (ch >= baseDir && *ch != '\\' && *ch != '/') + ch --; + + if (ch >= baseDir) + { + *ch = '\0'; + } + + return baseDir; +} + +static LDAPMod **buildMods () +{ + Slapi_Mods smods; + Slapi_Mod smod; + LDAPMod **mods; + struct berval bv; + int j, k; + + slapi_mods_init (&smods, MOD_COUNT); + + for (j = 0; j < MOD_COUNT; j++) + { + slapi_mod_init (&smod, VALUE_COUNT); + slapi_mod_set_operation (&smod, LDAP_MOD_ADD | LDAP_MOD_BVALUES); + slapi_mod_set_type (&smod, "attr"); + + for (k = 0; k < VALUE_COUNT; k++) + { + bv.bv_val = "bvalue"; + bv.bv_len = strlen (bv.bv_val) + 1; + slapi_mod_add_value (&smod, &bv); + } + + slapi_mods_add_smod (&smods, &smod); + /* ONREPL slapi_mod_done (&smod); */ + } + + mods = slapi_mods_get_ldapmods_passout (&smods); + slapi_mods_done (&smods); + return mods; +} + +/* Format: + dn: cn=changelog5,cn=config + objectclass: top + objectclass: extensibleObject + cn: changelog5 + nsslapd-changelogDir: d:/netscape/server4/slapd-elf/cl5 */ +static int configureChangelog () +{ + Slapi_PBlock *pb = slapi_pblock_new (); + Slapi_Entry *e = slapi_entry_alloc (); + int rc; + char *attrs[] = {INSTANCE_ATTR, NULL}; + Slapi_Entry **entries; + char cl_dir [256]; + char *str = NULL; + + /* set changelog dn */ + slapi_entry_set_dn (e, slapi_ch_strdup (CL_DN)); + + /* set object classes */ + slapi_entry_add_string(e, "objectclass", "top"); + slapi_entry_add_string(e, "objectclass", "extensibleObject"); + + /* get directory instance dir */ + slapi_search_internal_set_pb (pb, "cn=config", LDAP_SCOPE_BASE, "objectclass=*", + attrs, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to get server instance " + "directory; LDAP error - %d\n", rc); + rc = -1; + goto done; + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + str = slapi_entry_attr_get_charptr(entries[0], INSTANCE_ATTR); + sprintf (cl_dir, "%s/%s", str, "cl5db"); + slapi_ch_free((void **)&str); + slapi_entry_add_string (e, CONFIG_CHANGELOG_DIR_ATTRIBUTE, cl_dir); + + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + + pb = slapi_pblock_new (); + + slapi_add_entry_internal_set_pb (pb, e, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + + slapi_add_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add changelog " + "configuration entry; LDAP error - %d\n", rc); + rc = -1; + } + else + rc = 0; + +done: + slapi_pblock_destroy (pb); + + return rc; +} + +/* Format: + dn: cn=replica,cn="o=NetscapeRoot",cn= mapping tree,cn=config + objectclass: top + objectclass: nsds5Replica + objectclass: extensibleObject + nsds5ReplicaRoot: o=NetscapeRoot + nsds5ReplicaId: 2 + nsds5flags: 1 + cn: replica + */ +static int configureReplica () +{ + Slapi_PBlock *pb = slapi_pblock_new (); + Slapi_Entry *e = slapi_entry_alloc (); + int rc; + char dn [128]; + + /* set changelog dn */ + sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, REPLICA_ROOT, + slapi_get_mapping_tree_config_root ()); + slapi_entry_set_dn (e, slapi_ch_strdup (dn)); + + /* set object classes */ + slapi_entry_add_string(e, "objectclass", "top"); + slapi_entry_add_string(e, "objectclass", REPLICA_OC); + slapi_entry_add_string(e, "objectclass", "extensibleObject"); + + /* set other attributes */ + slapi_entry_add_string (e, attr_replicaRoot, REPLICA_ROOT); + slapi_entry_add_string (e, attr_replicaId, "1"); + slapi_entry_add_string (e, attr_flags, "1"); + + slapi_add_entry_internal_set_pb (pb, e, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + + slapi_add_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add replica for (%s) " + "configuration entry; LDAP error - %d\n", REPLICA_ROOT, rc); + rc = -1; + } + else + rc = 0; + + slapi_pblock_destroy (pb); + + return rc; +} + +/* generates one of each ldap operations */ +static int populateChangelogOp () +{ + Slapi_PBlock *pb = slapi_pblock_new (); + Slapi_Entry *e = slapi_entry_alloc (); + int rc; + char dn [128], newrdn [64]; + LDAPMod *mods[2]; + Slapi_Mod smod; + struct berval bv; + time_t cur_time; + + /* add entry */ + cur_time = time(NULL); + sprintf (dn, "cn=%s,%s", ctime(&cur_time), REPLICA_ROOT); + slapi_entry_set_dn (e, slapi_ch_strdup (dn)); + slapi_entry_add_string(e, "objectclass", "top"); + slapi_entry_add_string(e, "objectclass", "extensibleObject"); + slapi_entry_add_string (e, "mail", "jsmith@netscape.com"); + + slapi_add_entry_internal_set_pb (pb, e, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_add_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to add entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + /* modify entry */ + pb = slapi_pblock_new (); + slapi_mod_init (&smod, 1); + slapi_mod_set_type (&smod, "mail"); + slapi_mod_set_operation (&smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + bv.bv_val = "jsmith@aol.com"; + bv.bv_len = strlen (bv.bv_val); + slapi_mod_add_value(&smod, &bv); + mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod); + mods[1] = NULL; + slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_mod_done (&smod); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to modify entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + /* rename entry */ + pb = slapi_pblock_new (); + cur_time = time (NULL); + sprintf (newrdn, "cn=renamed%s", ctime(&cur_time)); + slapi_rename_internal_set_pb (pb, dn, newrdn, NULL, 1, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modrdn_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to rename entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + /* delete the entry */ + pb = slapi_pblock_new (); + sprintf (dn, "%s,%s", newrdn, REPLICA_ROOT); + slapi_delete_internal_set_pb (pb, dn, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_delete_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + slapi_pblock_destroy (pb); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "failed to delete entry (%s); " + "LDAP error - %d\n", dn, rc); + return -1; + } + + return 0; +} diff --git a/ldap/servers/plugins/replication/cl5_test.h b/ldap/servers/plugins/replication/cl5_test.h new file mode 100644 index 00000000..57d8435c --- /dev/null +++ b/ldap/servers/plugins/replication/cl5_test.h @@ -0,0 +1,21 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* cl5_test.h - changelog test cases */ + +typedef enum +{ + TEST_BASIC, /* open-close-delete, read-write-delete */ + TEST_BACKUP_RESTORE,/* test backup and recovery */ + TEST_ITERATION, /* similar to iteration used by replica upsate protocol */ + TEST_TRIMMING, /* test changelog trimming */ + TEST_PERFORMANCE, /* test read/write performance */ + TEST_PERFORMANCE_MT,/* test multithreaded performance */ + TEST_LDIF, /* test cl2ldif and ldif2cl */ + TEST_ALL /* collective test */ +} TestType; + +void testChangelog (TestType type); + diff --git a/ldap/servers/plugins/replication/csnpl.c b/ldap/servers/plugins/replication/csnpl.c new file mode 100644 index 00000000..7180af67 --- /dev/null +++ b/ldap/servers/plugins/replication/csnpl.c @@ -0,0 +1,328 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "csnpl.h" +#include "llist.h" +#include "repl_shared.h" + +struct csnpl +{ + LList* csnList; /* pending list */ + PRRWLock* csnLock; /* lock to serialize access to PL */ +}; + +typedef struct _csnpldata +{ + PRBool committed; /* True if CSN committed */ + CSN *csn; /* The actual CSN */ +} csnpldata; + +/* forward declarations */ +#ifdef DEBUG +static void _csnplDumpContentNoLock(CSNPL *csnpl, const char *caller); +#endif + +CSNPL* csnplNew () +{ + CSNPL *csnpl; + + csnpl = (CSNPL *)slapi_ch_malloc (sizeof (CSNPL)); + if (csnpl == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplNew: failed to allocate pending list\n"); + return NULL; + } + + csnpl->csnList = llistNew (); + if (csnpl->csnList == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplNew: failed to allocate pending list\n"); + slapi_ch_free ((void**)&csnpl); + return NULL; + } + + /* ONREPL: do locks need different names */ + csnpl->csnLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "pl_lock"); + + if (csnpl->csnLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplNew: failed to create lock; NSPR error - %d\n", + PR_GetError ()); + slapi_ch_free ((void**)&(csnpl->csnList)); + slapi_ch_free ((void**)&csnpl); + return NULL; + } + + return csnpl; +} + + +void +csnpldata_free(void **data) +{ + csnpldata **data_to_free = (csnpldata **)data; + if (NULL != data_to_free) + { + if (NULL != (*data_to_free)->csn) + { + csn_free(&(*data_to_free)->csn); + } + slapi_ch_free((void **)data_to_free); + } +} + +void csnplFree (CSNPL **csnpl) +{ + if ((csnpl == NULL) || (*csnpl == NULL)) + return; + + /* free all remaining nodes */ + llistDestroy (&((*csnpl)->csnList), (FNFree)csnpldata_free); + + if ((*csnpl)->csnLock); + PR_DestroyRWLock ((*csnpl)->csnLock); + + slapi_ch_free ((void**)csnpl); +} + +/* This function isnerts a CSN into the pending list + * Returns: 0 if the csn was successfully inserted + * 1 if the csn has already been seen + * -1 for any other kind of errors + */ +int csnplInsert (CSNPL *csnpl, const CSN *csn) +{ + int rc; + csnpldata *csnplnode; + char csn_str[CSN_STRSIZE]; + + if (csnpl == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplInsert: invalid argument\n"); + return -1; + } + + PR_RWLock_Wlock (csnpl->csnLock); + + /* check to see if this csn is larger than the last csn in the + pending list. It has to be if we have not seen it since + the csns are always added in the accending order. */ + csnplnode = llistGetTail (csnpl->csnList); + if (csnplnode && csn_compare (csnplnode->csn, csn) >= 0) + { + PR_RWLock_Unlock (csnpl->csnLock); + return 1; + } + + csnplnode = (csnpldata *)slapi_ch_malloc(sizeof(csnpldata)); + csnplnode->committed = PR_FALSE; + csnplnode->csn = csn_dup(csn); + csn_as_string(csn, PR_FALSE, csn_str); + rc = llistInsertTail (csnpl->csnList, csn_str, csnplnode); + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplInsert"); +#endif + + PR_RWLock_Unlock (csnpl->csnLock); + if (rc != 0) + { + char s[CSN_STRSIZE]; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "csnplInsert: failed to insert csn (%s) into pending list\n", csn_as_string(csn,PR_FALSE,s)); + return -1; + } + + return 0; +} + +int csnplRemove (CSNPL *csnpl, const CSN *csn) +{ + csnpldata *data; + char csn_str[CSN_STRSIZE]; + + if (csnpl == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplRemove: invalid argument\n"); + return -1; + } + + csn_as_string(csn, PR_FALSE, csn_str); + PR_RWLock_Wlock (csnpl->csnLock); + + data = (csnpldata *)llistRemove (csnpl->csnList, csn_str); + if (data == NULL) + { + PR_RWLock_Unlock (csnpl->csnLock); + return -1; + } + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplRemove"); +#endif + + csn_free(&data->csn); + slapi_ch_free((void **)&data); + + PR_RWLock_Unlock (csnpl->csnLock); + + return 0; +} + +int csnplCommit (CSNPL *csnpl, const CSN *csn) +{ + csnpldata *data; + char csn_str[CSN_STRSIZE]; + + if (csnpl == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplCommit: invalid argument\n"); + return -1; + } + csn_as_string(csn, PR_FALSE, csn_str); + + PR_RWLock_Wlock (csnpl->csnLock); + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplCommit"); +#endif + + data = (csnpldata*)llistGet (csnpl->csnList, csn_str); + if (data == NULL) + { + /* + * In the scenario "4.x master -> 6.x legacy-consumer -> 6.x consumer" + * csn will have rid=65535. Hence 6.x consumer will get here trying + * to commit r->min_csn_pl because its rid matches that in the csn. + * However, r->min_csn_pl is always empty for a dedicated consumer. + * Exclude READ-ONLY replica ID here from error logging. + */ + ReplicaId rid = csn_get_replicaid (csn); + if (rid < MAX_REPLICA_ID) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "csnplCommit: can't find csn %s\n", csn_str); + } + PR_RWLock_Unlock (csnpl->csnLock); + return -1; + } + else + { + data->committed = PR_TRUE; + } + + PR_RWLock_Unlock (csnpl->csnLock); + + return 0; +} + + + +CSN* csnplGetMinCSN (CSNPL *csnpl, PRBool *committed) +{ + csnpldata *data; + CSN *csn = NULL; + PR_RWLock_Rlock (csnpl->csnLock); + if ((data = (csnpldata*)llistGetHead (csnpl->csnList)) != NULL) + { + csn = csn_dup(data->csn); + if (NULL != committed) + { + *committed = data->committed; + } + } + PR_RWLock_Unlock (csnpl->csnLock); + + return csn; +} + + +/* + * Roll up the list of pending CSNs, removing all of the CSNs at the + * head of the the list that are committed and contiguous. Returns + * the largest committed CSN, or NULL if no contiguous block of + * committed CSNs appears at the beginning of the list. The caller + * is responsible for freeing the CSN returned. + */ +CSN * +csnplRollUp(CSNPL *csnpl, CSN **first_commited) +{ + CSN *largest_committed_csn = NULL; + csnpldata *data; + PRBool freeit = PR_TRUE; + + PR_RWLock_Wlock (csnpl->csnLock); + if (first_commited) { + /* Avoid non-initialization issues due to careless callers */ + *first_commited = NULL; + } + data = (csnpldata *)llistGetHead(csnpl->csnList); + while (NULL != data && data->committed) + { + if (NULL != largest_committed_csn && freeit) + { + csn_free(&largest_committed_csn); + } + freeit = PR_TRUE; + largest_committed_csn = data->csn; /* Save it */ + if (first_commited && (*first_commited == NULL)) { + *first_commited = data->csn; + freeit = PR_FALSE; + } + data = (csnpldata*)llistRemoveHead (csnpl->csnList); + slapi_ch_free((void **)&data); + data = (csnpldata *)llistGetHead(csnpl->csnList); + } + +#ifdef DEBUG + _csnplDumpContentNoLock(csnpl, "csnplRollUp"); +#endif + + PR_RWLock_Unlock (csnpl->csnLock); + return largest_committed_csn; +} + +#ifdef DEBUG +/* Dump current content of the list - for debugging */ +void +csnplDumpContent(CSNPL *csnpl, const char *caller) +{ + if (csnpl) + { + PR_RWLock_Rlock (csnpl->csnLock); + _csnplDumpContentNoLock (csnpl, caller); + PR_RWLock_Unlock (csnpl->csnLock); + } +} + +/* helper function */ +static void _csnplDumpContentNoLock(CSNPL *csnpl, const char *caller) +{ + csnpldata *data; + void *iterator; + char csn_str[CSN_STRSIZE]; + + data = (csnpldata *)llistGetFirst(csnpl->csnList, &iterator); + if (data) { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: CSN Pending list content:\n", + caller ? caller : ""); + } + while (data) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s, %s\n", + csn_as_string(data->csn, PR_FALSE, csn_str), + data->committed ? "committed" : "not committed"); + data = (csnpldata *)llistGetNext (csnpl->csnList, &iterator); + } +} +#endif + diff --git a/ldap/servers/plugins/replication/csnpl.h b/ldap/servers/plugins/replication/csnpl.h new file mode 100644 index 00000000..ae1b4c85 --- /dev/null +++ b/ldap/servers/plugins/replication/csnpl.h @@ -0,0 +1,23 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* csnpl.h - interface for csn pending list */ + +#ifndef CSNPL_H +#define CSNPL_H + +#include "slapi-private.h" + +typedef struct csnpl CSNPL; + +CSNPL* csnplNew (); +void csnplFree (CSNPL **csnpl); +int csnplInsert (CSNPL *csnpl, const CSN *csn); +int csnplRemove (CSNPL *csnpl, const CSN *csn); +CSN* csnplGetMinCSN (CSNPL *csnpl, PRBool *committed); +int csnplCommit (CSNPL *csnpl, const CSN *csn); +CSN *csnplRollUp(CSNPL *csnpl, CSN ** first); +void csnplDumpContent(CSNPL *csnpl, const char *caller); +#endif diff --git a/ldap/servers/plugins/replication/dllmain.c b/ldap/servers/plugins/replication/dllmain.c new file mode 100644 index 00000000..3f17b14c --- /dev/null +++ b/ldap/servers/plugins/replication/dllmain.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + /* + * Microsoft Windows specifics for LIBREPLICATION DLL + */ +#include "ldap.h" + + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; /* successful DLL_PROCESS_ATTACH */ +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif diff --git a/ldap/servers/plugins/replication/legacy_consumer.c b/ldap/servers/plugins/replication/legacy_consumer.c new file mode 100644 index 00000000..8bf45ee1 --- /dev/null +++ b/ldap/servers/plugins/replication/legacy_consumer.c @@ -0,0 +1,707 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * repl_legacy_consumer.c - support for legacy replication (consumer-side) + * + * Support for legacy replication involves correctly dealing with + * the addition and removal of attribute types "copiedFrom" and + * "copyingFrom". The behavior is: + * 1) If a copiedFrom appears in an entry, and that entry is the root + * of a replicated area, then put the backend into "refer on update" + * mode and install a referral corresponding to the URL contained + * in the copiedFrom attribute. This referral overrides the mode + * of the replica, e.g. if it was previously an updateable replica, + * it now becomes read-only except for the updatedn. + * 2) If a copiedFrom disappears from an entry, or the entry containing + * the copiedFrom is removed, restore the backend to the state + * determined by the DS 5.0 replica configuration. + * 3) If a "copyingFrom" referral appears in an entry, and that entry + * is the root of a replicated area, then put the backend into + * "refer all operations" mode and install a referral corresponding + * to the URL contained in the copyingFrom attribute. This referral + * overrides the mode of the replica, e.g if it was previously an + * updateable replica, it now becomes read-only and refers all + * operations except for the updatedn. + * 4) If a copyingFrom disappears from an entry, or the entry containing + * the copyingFrom is removed, restore the backend to the state + * determined by the DS 5.0 replica configuration. + */ + + +#include "repl5.h" +#include "repl.h" + +/* Forward Declarations */ +static int legacy_consumer_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int legacy_consumer_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int legacy_consumer_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +static int legacy_consumer_extract_config(Slapi_Entry* entry, char *returntext); +static int legacy_consumer_read_config (); +static void legacy_consumer_encode_pw (Slapi_Entry *e); +static void set_legacy_purl (Slapi_PBlock *pb, const char *purl); +static int get_legacy_referral (Slapi_Entry *e, char **referral, char **state); + +#define LEGACY_CONSUMER_CONFIG_DN "cn=legacy consumer," REPL_CONFIG_TOP +#define LEGACY_CONSUMER_FILTER "(objectclass=*)" + +/* Configuration parameters local to this module */ +static Slapi_DN *legacy_consumer_replicationdn = NULL; +static char *legacy_consumer_replicationpw = NULL; +/* Lock which protects the above config parameters */ +PRRWLock *legacy_consumer_config_lock = NULL; + +static PRBool +target_is_a_replica_root(Slapi_PBlock *pb, const Slapi_DN **root) +{ + char *dn; + Slapi_DN *sdn; + PRBool return_value; + Object *repl_obj; + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + sdn = slapi_sdn_new_dn_byref(dn); + repl_obj = replica_get_replica_from_dn(sdn); + if (NULL != repl_obj) + { + Replica *r = object_get_data(repl_obj); + *root = replica_get_root(r); + return_value = PR_TRUE; + object_release(repl_obj); + } + else + { + *root = NULL; + return_value = PR_FALSE; + } + slapi_sdn_free(&sdn); + return return_value; +} + + + +static int +parse_cfstring(const char *cfstring, char **referral, char **generation, char **lastreplayed) +{ + int return_value = -1; + char *ref, *gen, *lastplayed; + + if (cfstring != NULL) + { + char *tmp; + char *cfcopy = slapi_ch_strdup(cfstring); + ref = cfcopy; + tmp = strchr(cfcopy, ' '); + if (NULL != tmp) + { + *tmp++ = '\0'; + while ('\0' != *tmp && ' ' == *tmp) tmp++; + gen = tmp; + tmp = strchr(gen, ' '); + if (NULL != tmp) + { + *tmp++ = '\0'; + while ('\0' != *tmp && ' ' == *tmp) tmp++; + lastplayed = tmp; + return_value = 0; + } + } + + if (return_value == 0) + { + if (referral) + *referral = slapi_ch_strdup(ref); + if (generation) + *generation = slapi_ch_strdup(gen); + if (lastreplayed) + *lastreplayed = slapi_ch_strdup(lastplayed); + } + slapi_ch_free((void **)&cfcopy); + } + return return_value; +} + + + +/* + * This is called from the consumer post-op plugin point. + * It's called if: + * 1) The operation is an add or modify operation, and a + * copiedfrom/copyingfrom was found in the entry/mods, or + * 2) the operation is a delete operation, or + * 3) the operation is a moddn operation. + */ + +void +process_legacy_cf(Slapi_PBlock *pb) +{ + consumer_operation_extension *opext; + Slapi_Operation *op; + char *referral_array[2] = {0}; + char *referral; + char *state; + int rc; + const Slapi_DN *replica_root_sdn = NULL; + Slapi_Entry *e; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, op); + + if (opext->has_cf) + { + PR_ASSERT (operation_get_type (op) == SLAPI_OPERATION_ADD || + operation_get_type (op) == SLAPI_OPERATION_MODIFY); + + if ((PR_FALSE == target_is_a_replica_root(pb, &replica_root_sdn)) || + (NULL == replica_root_sdn)){ + return; + } + + slapi_pblock_get (pb, SLAPI_ENTRY_POST_OP, &e); + PR_ASSERT (e); + + if (NULL == e) + return; + + rc = get_legacy_referral (e, &referral, &state); + if (rc == 0) + { + referral_array[0] = referral; + referral_array[1] = NULL; + repl_set_mtn_state_and_referrals(replica_root_sdn, state, NULL, NULL, + referral_array); + /* set partial url in the replica_object */ + set_legacy_purl (pb, referral); + + slapi_ch_free((void **)&referral); + } + + } +} + +void legacy_consumer_be_state_change (void *handle, char *be_name, + int old_be_state, int new_be_state) +{ + Object *r_obj; + Replica *r; + + /* we only interested when a backend is coming online */ + if (new_be_state == SLAPI_BE_STATE_ON) + { + r_obj = replica_get_for_backend (be_name); + if (r_obj) + { + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + if (replica_is_legacy_consumer (r)) + legacy_consumer_init_referrals (r); + + object_release (r_obj); + } + } +} + + +static int +dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg) +{ + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +int +legacy_consumer_config_init() +{ + /* The FE DSE *must* be initialised before we get here */ + int rc; + + if ((legacy_consumer_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "legacy_consumer_config_lock")) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Failed to create legacy_consumer config read-write lock\n"); + exit(1); + } + + rc = legacy_consumer_read_config (); + if (rc != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Failed to initialize legacy replication configuration\n"); + return 1; + } + + slapi_config_register_callback(SLAPI_OPERATION_ADD,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_add,NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_modify,NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,dont_allow_that,NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE,DSE_FLAG_PREOP,LEGACY_CONSUMER_CONFIG_DN,LDAP_SCOPE_SUBTREE,LEGACY_CONSUMER_FILTER,legacy_consumer_config_delete,NULL); + + return 0; +} + +static int +legacy_consumer_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + int rc; + + rc = legacy_consumer_extract_config(e, returntext); + if (rc != LDAP_SUCCESS) + { + *returncode = rc; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Failed to configure legacy replication\n"); + return SLAPI_DSE_CALLBACK_ERROR; + } + /* make sure that the password is encoded */ + legacy_consumer_encode_pw(e); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "legacy_consumer_config_add: " + "successfully configured legacy consumer credentials\n"); + + return SLAPI_DSE_CALLBACK_OK; +} + +#define config_copy_strval( s ) s ? slapi_ch_strdup (s) : NULL; + +static int +legacy_consumer_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg) +{ + int rc= 0; + LDAPMod **mods; + int not_allowed = 0; + int i; + + if (returntext) + { + returntext[0] = '\0'; + } + *returncode = LDAP_SUCCESS; + + + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods ); + PR_RWLock_Wlock (legacy_consumer_config_lock); + + for (i = 0; (mods[i] && (!not_allowed)); i++) + { + if (mods[i]->mod_op & LDAP_MOD_DELETE) + { + /* We don't support deleting an attribute from cn=config */ + } + else + { + int j; + for (j = 0; ((mods[i]->mod_values[j]) && (LDAP_SUCCESS == rc)); j++) + { + char *config_attr, *config_attr_value; + int mod_type; + config_attr = (char *) mods[i]->mod_type; + config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val; + /* replace existing value */ + mod_type = mods[i]->mod_op & ~LDAP_MOD_BVALUES; + if ( strcasecmp (config_attr, CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE ) == 0 ) + { + if (legacy_consumer_replicationdn) + slapi_sdn_free (&legacy_consumer_replicationdn); + + if (mod_type == LDAP_MOD_REPLACE) + { + if (config_attr_value) + legacy_consumer_replicationdn = slapi_sdn_new_dn_byval (config_attr_value); + } + else if (mod_type == LDAP_MOD_DELETE) + { + legacy_consumer_replicationdn = NULL; + } + else if (mod_type == LDAP_MOD_ADD) + { + if (legacy_consumer_replicationdn != NULL) + { + not_allowed = 1; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Multiple replicationdns not permitted." ); + } + else + { + if (config_attr_value) + legacy_consumer_replicationdn = slapi_sdn_new_dn_byval (config_attr_value); + } + } + } + else if ( strcasecmp ( config_attr, CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE ) == 0 ) + { + if (mod_type == LDAP_MOD_REPLACE) + { + legacy_consumer_replicationpw = config_copy_strval(config_attr_value); + } + else if (mod_type == LDAP_MOD_DELETE) + { + legacy_consumer_replicationpw = NULL; + } + else if (mod_type == LDAP_MOD_ADD) + { + if (legacy_consumer_replicationpw != NULL) + { + not_allowed = 1; + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Multiple replicationpws not permitted." ); + } + else + { + legacy_consumer_replicationpw = config_copy_strval(config_attr_value); + } + } + } + } + } + } + + PR_RWLock_Unlock (legacy_consumer_config_lock); + + + if (not_allowed) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "Failed to modify legacy replication configuration\n" ); + *returncode= LDAP_CONSTRAINT_VIOLATION; + return SLAPI_DSE_CALLBACK_ERROR; + } + + /* make sure that the password is encoded */ + legacy_consumer_encode_pw (e); + + return SLAPI_DSE_CALLBACK_OK; +} + +static int +legacy_consumer_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + + PR_RWLock_Wlock (legacy_consumer_config_lock); + if (legacy_consumer_replicationdn) + slapi_sdn_free (&legacy_consumer_replicationdn); + if (legacy_consumer_replicationpw) + slapi_ch_free ((void**)&legacy_consumer_replicationpw); + + legacy_consumer_replicationdn = NULL; + legacy_consumer_replicationpw = NULL; + PR_RWLock_Unlock (legacy_consumer_config_lock); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +/* + * Given the changelog configuration entry, extract the configuration directives. + */ +static int +legacy_consumer_extract_config(Slapi_Entry* entry, char *returntext) +{ + int rc = LDAP_SUCCESS; /* OK */ + char *arg; + + PR_RWLock_Wlock (legacy_consumer_config_lock); + + arg= slapi_entry_attr_get_charptr(entry,CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE); + if (arg) + legacy_consumer_replicationdn = slapi_sdn_new_dn_passin (arg); + + arg= slapi_entry_attr_get_charptr(entry,CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE); + legacy_consumer_replicationpw = arg; + + PR_RWLock_Unlock (legacy_consumer_config_lock); + + return rc; +} + + + + +static int +legacy_consumer_read_config () +{ + int rc = LDAP_SUCCESS; + int scope= LDAP_SCOPE_BASE; + Slapi_PBlock *pb; + + pb = slapi_pblock_new (); + slapi_search_internal_set_pb (pb, LEGACY_CONSUMER_CONFIG_DN, scope, + "(objectclass=*)", NULL /*attrs*/, 0 /* attrs only */, + NULL /* controls */, NULL /* uniqueid */, + repl_get_plugin_identity(PLUGIN_LEGACY_REPLICATION), 0 /* actions */); + slapi_search_internal_pb (pb); + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc ); + if ( LDAP_SUCCESS == rc ) + { + Slapi_Entry **entries = NULL; + slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries ); + if ( NULL != entries && NULL != entries[0]) + { + /* Extract the config info from the changelog entry */ + rc = legacy_consumer_extract_config(entries[0], NULL); + } + } + else + { + rc = LDAP_SUCCESS; + } + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return rc; +} + + +int +legacy_consumer_is_replicationdn(char *dn) +{ + int return_value = 0; /* Assume not */ + + if (NULL != dn && '\0' != dn[0]) + { + if (NULL != legacy_consumer_replicationdn) + { + Slapi_DN *sdn = slapi_sdn_new_dn_byref (dn); + + if (slapi_sdn_compare (legacy_consumer_replicationdn, sdn) == 0) { + return_value = 1; + } + + slapi_sdn_free (&sdn); + } + } + return return_value; +} + + +int +legacy_consumer_is_replicationpw(struct berval *pwval) +{ + int return_value = 0; /* Assume not */ + + if (NULL != pwval && NULL != pwval->bv_val) + { + if (NULL != legacy_consumer_replicationpw && + '\0' != legacy_consumer_replicationpw[0]) { + struct berval *pwvals[2]; + struct berval config_pw; + + config_pw.bv_val = legacy_consumer_replicationpw; + config_pw.bv_len = strlen(legacy_consumer_replicationpw); + pwvals[0] = &config_pw; + pwvals[1] = NULL; + + return_value = slapi_pw_find(pwvals, pwval) == 0; + } + } + return return_value; +} + +static void +legacy_consumer_free_config () +{ + if (NULL != legacy_consumer_replicationdn) { + slapi_sdn_free(&legacy_consumer_replicationdn); + } + if (NULL != legacy_consumer_replicationpw) { + slapi_ch_free((void **) &legacy_consumer_replicationpw); + } +} + + + +static void +legacy_consumer_encode_pw (Slapi_Entry *e) +{ + char *updatepw = slapi_entry_attr_get_charptr(e, + CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE); + int is_encoded; + char *encoded_value = NULL; + + if (updatepw != NULL) + { + is_encoded = slapi_is_encoded (updatepw); + + if (!is_encoded) + { + encoded_value = slapi_encode (updatepw, "SHA"); + } + + if (encoded_value) + { + slapi_entry_attr_set_charptr(e, + CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE, encoded_value); + } + } +} + +static void +set_legacy_purl (Slapi_PBlock *pb, const char *purl) +{ + Object *r_obj; + Replica *r; + + r_obj = replica_get_replica_for_op (pb); + PR_ASSERT (r_obj); + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r && replica_is_legacy_consumer(r)); + + replica_set_legacy_purl (r, purl); + + object_release (r_obj); +} + +/* this function get referrals from an entry. + Returns 0 if successful + 1 if no referrals are present + -1 in case of error + */ +static int +get_legacy_referral (Slapi_Entry *e, char **referral, char **state) +{ + char* pat = "ldap://%s"; + const char *val = NULL; + char *hostport; + int rc = 1; + Slapi_Attr *attr; + const Slapi_Value *sval; + + PR_ASSERT (e && referral && state); + + /* Find any copiedFrom/copyingFrom attributes - + copyingFrom has priority */ + if (slapi_entry_attr_find(e, type_copyingFrom, &attr) == 0) + { + slapi_attr_first_value(attr, (Slapi_Value **)&sval); + val = slapi_value_get_string(sval); + *state = STATE_REFERRAL; + } + else if (slapi_entry_attr_find(e, type_copiedFrom, &attr) == 0) + { + slapi_attr_first_value(attr, (Slapi_Value **)&sval); + val = slapi_value_get_string(sval); + *state = STATE_UPDATE_REFERRAL; + } + + if (val) + { + rc = parse_cfstring(val, &hostport, NULL, NULL); + if (rc != 0) + { + const char *target_dn = slapi_entry_get_dn_const(e); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: a copiedFrom " + "or copyingFrom attribute was added to or removed from an " + "entry that is not the root of a replicated area. It is possible " + "that a legacy replication supplier is incorrectly configured " + "to supply updates to the subtree rooted at %s\n", + target_dn == NULL ? "null" : target_dn); + } + else + { + *referral = slapi_ch_malloc (strlen (pat) + strlen (hostport)); + + sprintf (*referral, pat, hostport); + + slapi_ch_free ((void**)&hostport); + } + } + else + { + rc = 1; /* no copiedFrom or copyingFrom int the entry */ + } + + return rc; +} + +/* this function is called during server startup or when replica's data + is reloaded. It sets up referrals in the mapping tree based on the + copiedFrom and copyingFrom attributes. It also sets up partial url in + the replica object used to update RUV. + Returns 0 if successful and -1 otherwise + + */ +int +legacy_consumer_init_referrals (Replica *r) +{ + Slapi_PBlock *pb; + const Slapi_DN *root_sdn; + const char *root_dn; + char *attrs[] = {"copiedFrom", "copyingFrom"}; + int rc; + Slapi_Entry **entries = NULL; + char *referral = NULL; + char *referral_array[2]; + char *state = NULL; + + PR_ASSERT (r); + + pb = slapi_pblock_new (); + PR_ASSERT (pb); + + root_sdn = replica_get_root(r); + PR_ASSERT (root_sdn); + + root_dn = slapi_sdn_get_ndn(root_sdn); + PR_ASSERT (root_dn); + + slapi_search_internal_set_pb (pb, root_dn, LDAP_SCOPE_BASE, "objectclass=*",attrs, + 0 /* attrsonly */, NULL /* controls */, + NULL /* uniqueid */, + repl_get_plugin_identity (PLUGIN_LEGACY_REPLICATION), + 0 /* flags */); + + slapi_search_internal_pb (pb); + + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + if (rc == LDAP_REFERRAL) + { + /* We are in referral mode, probably because ORC failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "legacy_consumer_init_referrals " + "data for replica %s is in referral mode due to failed " + "initialization. Replica need to be reinitialized\n", + root_dn); + rc = 0; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "legacy_consumer_init_referrals " + "failed to obtain root entry for replica %s; LDAP error - %d\n", + root_dn, rc); + rc = -1; + } + + goto done; + } + + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + + PR_ASSERT (entries && entries[0]); + + rc = get_legacy_referral (entries[0], &referral, &state); + if (rc == 0) + { + referral_array[0] = referral; + referral_array[1] = NULL; + repl_set_mtn_state_and_referrals(root_sdn, state, NULL, NULL, referral_array); + + /* set purtial url in the replica_object */ + replica_set_legacy_purl (r, referral); + + slapi_ch_free((void **)&referral); + } + else if (rc == 1) /* no referrals - treat as success */ + { + rc = 0; + } + + slapi_free_search_results_internal (pb); + +done: + + slapi_pblock_destroy (pb); + return rc; +} + diff --git a/ldap/servers/plugins/replication/llist.c b/ldap/servers/plugins/replication/llist.c new file mode 100644 index 00000000..175ea48f --- /dev/null +++ b/ldap/servers/plugins/replication/llist.c @@ -0,0 +1,336 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* llist.c - single link list implementation */ + +#include <string.h> +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "llist.h" +#include "repl_shared.h" + +/* data structures */ + +/* link list node */ +typedef struct lnode +{ + char *key; + void *data; + struct lnode *next; +} LNode; + +/* This structure defines a one-way linked list with head and tail pointers. + The list contains a "dummy" head node which makes sure that every node + has a previous node. This allows to remove a node during iteration without + breaking the list */ +struct llist +{ + LNode *head; + LNode *tail; +}; + +/* forward declarations */ +static LNode* _llistNewNode (const char *key, void *data); +static void _llistDestroyNode (LNode **node, FNFree fnFree); + +LList* llistNew () +{ + LList *list = (LList*) slapi_ch_calloc (1, sizeof (LList)); + + /* allocate a special head node - it contains no data but just + fulfills the requirement that every node has a previous one. + This is used during iteration with removal */ + if (list) + { + list->head = (LNode*)slapi_ch_calloc (1, sizeof (LNode)); + if (list->head == NULL) + { + slapi_ch_free ((void**)&list); + } + } + + return list; +} + +void llistDestroy (LList **list, FNFree fnFree) +{ + LNode *node = NULL, *prev_node; + + if (list == NULL || *list == NULL) + return; + + if ((*list)->head) + node = (*list)->head->next; + + while (node) + { + prev_node = node; + node = node->next; + _llistDestroyNode (&prev_node, fnFree); + } + + slapi_ch_free ((void**)&((*list)->head)); + slapi_ch_free ((void**)list); +} + +void* llistGetFirst(LList *list, void **iterator) +{ + if (list == NULL || iterator == NULL || list->head == NULL || list->head->next == NULL) + { + /* empty list or error */ + return NULL; + } + + /* Iterator points to the previous element (so that we can remove current element + and still keep the list in tact. In case of the first element, iterator points + to the dummy head element */ + (*iterator) = list->head; + return list->head->next->data; +} + +void* llistGetNext (LList *list, void **iterator) +{ + LNode *node; + + if (list == NULL || list->head == NULL || iterator == NULL || *iterator == NULL) + { + /* end of the list or error */ + return NULL; + } + + /* Iterator points to the previous element (so that we can + remove current element and still keep list in tact. */ + node = *(LNode **)iterator; + node = node->next; + + (*iterator) = node; + + if (node && node->next) + return node->next->data; + else + return NULL; +} + +void* llistRemoveCurrentAndGetNext (LList *list, void **iterator) +{ + LNode *prevNode, *node; + + /* end of the list is reached or error occured */ + if (list == NULL || iterator == NULL || *iterator == NULL) + return NULL; + + /* Iterator points to the previous element (so that we can + remove current element and still keep list in tact. */ + prevNode = *(LNode **)iterator; + node = prevNode->next; + if (node) + { + prevNode->next = node->next; + _llistDestroyNode (&node, NULL); + node = prevNode->next; + if (node) + return node->data; + else + return NULL; + } + else + return NULL; +} + +void* llistGetHead (LList *list) +{ + if (list == NULL || list->head == NULL || list->head->next == NULL) + { + /* empty list or error */ + return NULL; + } + + return list->head->next->data; +} + +void* llistGetTail (LList *list) +{ + if (list == NULL || list->tail == NULL) + { + /* empty list or error */ + return NULL; + } + + return list->tail->data; +} + +void* llistGet (LList *list, const char* key) +{ + LNode *node; + + /* empty list or invalid input */ + if (list == NULL || list->head == NULL || list->head->next == NULL || key == NULL) + return NULL; + + node = list->head->next; + while (node) + { + if (node->key && strcmp (key, node->key) == 0) + { + return node->data; + } + + node = node->next; + } + + /* node with specified key is not found */ + return NULL; +} + +int llistInsertHead (LList *list, const char *key, void *data) +{ + LNode *node; + if (list == NULL || list->head == NULL || data == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: invalid argument\n"); + return -1; + } + + node = _llistNewNode (key, data); + if (node == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: failed to allocate list node\n"); + return -1; + } + + if (list->head->next == NULL) /* empty list */ + { + list->head->next = node; + list->tail = node; + } + else + { + node->next = list->head->next; + list->head->next = node; + } + + return 0; +} + +int llistInsertTail (LList *list, const char *key, void *data) +{ + LNode *node; + if (list == NULL || list->head == NULL || data == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: invalid argument\n"); + return -1; + } + + node = _llistNewNode (key, data); + if (node == NULL) + { + slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name, "llistInsertHead: failed to allocate list node\n"); + return -1; + } + + if (list->head->next == NULL) /* empty list */ + { + list->head->next = node; + list->tail = node; + } + else + { + list->tail->next = node; + list->tail = node; + } + + return 0; +} + +void* llistRemoveHead (LList *list) +{ + LNode *node; + void *data; + + if (list == NULL || list->head == NULL || list->head->next == NULL) + return NULL; + + node = list->head->next; + data = node->data; + + list->head->next = node->next; + + /* last element removed */ + if (list->head->next == NULL) + list->tail = NULL; + + _llistDestroyNode (&node, NULL); + + return data; +} + +void* llistRemove (LList *list, const char *key) +{ + LNode *node, *prev_node; + void *data; + + if (list == NULL || list->head == NULL || list->head->next == NULL || key == NULL) + return NULL; + + node = list->head->next; + prev_node = list->head; + while (node) + { + if (node->key && strcmp (key, node->key) == 0) + { + prev_node->next = node->next; + /* last element removed */ + if (node->next == NULL) + { + /* no more elements in the list */ + if (list->head->next == NULL) + { + list->tail = NULL; + } + else + { + list->tail = prev_node; + } + } + + data = node->data; + _llistDestroyNode (&node, NULL); + return data; + } + + prev_node = node; + node = node->next; + } + + /* node with specified key is not found */ + return NULL; +} + +static LNode* _llistNewNode (const char *key, void *data) +{ + LNode *node = (LNode*) slapi_ch_malloc (sizeof (LNode)); + if (node == NULL) + return NULL; + + if (key) + node->key = slapi_ch_strdup (key); + else + node->key = NULL; + + node->data = data; + node->next = NULL; + + return node; +} + +static void _llistDestroyNode (LNode **node, FNFree fnFree) +{ + if ((*node)->data && fnFree) + fnFree (&(*node)->data); + if ((*node)->key) + slapi_ch_free ((void**)&((*node)->key)); + + slapi_ch_free ((void**)node); +} diff --git a/ldap/servers/plugins/replication/llist.h b/ldap/servers/plugins/replication/llist.h new file mode 100644 index 00000000..3b196ef8 --- /dev/null +++ b/ldap/servers/plugins/replication/llist.h @@ -0,0 +1,26 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* llist.h - single link list interface */ + +#ifndef LLIST_H +#define LLIST_H +typedef struct llist LList; + +LList* llistNew (); +void llistDestroy (LList **list, FNFree fnFree); +void* llistGetFirst(LList *list, void **iterator); +void* llistGetNext (LList *list, void **iterator); +void* llistRemoveCurrentAndGetNext (LList *list, void **iterator); +void* llistGetHead (LList *list); +void* llistGetTail (LList *list); +void* llistGet (LList *list, const char* key); +int llistInsertHead (LList *list, const char *key, void *data); +int llistInsertTail (LList *list, const char *key, void *data); +void* llistRemoveHead (LList *list); +void* llistRemove (LList *list, const char *key); + +#endif + diff --git a/ldap/servers/plugins/replication/profile.c b/ldap/servers/plugins/replication/profile.c new file mode 100644 index 00000000..0a7de374 --- /dev/null +++ b/ldap/servers/plugins/replication/profile.c @@ -0,0 +1,42 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + + +/* module: provide an interface to the profile file */ + +static FILE *profile_fd=NULL; + +/* JCMREPL - Could build up in an AVL tree and dump out to disk at the end... */ + +void profile_log(char *file,int line) +{ + if (profile_fd==NULL) + slapi_log_error(,"profile_log: profile file not open."); + else + { + /* JCMREPL - Probably need a lock around here */ + fprintf(profile_fd,"%s %d\n",file,line); + } +} + +void profile_open() +{ + char filename[MAX_FILENAME]; + strncpy(filename,CFG_rootpath,MAX_FILENAME); + strcat(filename,CFG_profilefile); + profile_fd= textfile_open(filename,"a"); +} + +void profile_close() +{ + if (profile_fd==NULL) + slapi_log_error(,"profile_close: profile file not open."); + else + textfile_close(profile_fd); +} diff --git a/ldap/servers/plugins/replication/repl.h b/ldap/servers/plugins/replication/repl.h new file mode 100644 index 00000000..8e502816 --- /dev/null +++ b/ldap/servers/plugins/replication/repl.h @@ -0,0 +1,366 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _REPL_H_ +#define _REPL_H_ + +#include <limits.h> +#include <time.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <sys/param.h> +#endif /* _WIN32 */ + +#include "portable.h" /* GGOODREPL - is this cheating? */ +#include "ldaplog.h" +#include "repl_shared.h" +#include "cl4.h" + +typedef struct schedule_item +{ + unsigned long sch_start; /* seconds after midnight */ + unsigned long sch_length; /* sec */ + unsigned int sch_weekdays; /* bit mask; LSB = Sunday */ + struct schedule_item* sch_next; +} schedule_item; + +/* XXXggood - copied from slap.h - bad */ +#if defined( XP_WIN32 ) +#define NO_TIME (time_t)0 /* cannot be -1, NT's localtime( -1 ) returns NULL */ +#else +#define NO_TIME (time_t)-1 /* a value that time() does not return */ +#endif + +/* + * A status message contains a time, the textual message, + * and a count of the number of times the message occured. + */ +typedef struct _status_message { + time_t sm_time; + char *sm_message; + int sm_occurances; +} status_message; + +/* + * A status_message_list is a circular array of status messages. + * Old messages roll off the end and are discarded. + */ +typedef struct _status_message_list { + int sml_size; /* number of slots in array */ + int sml_tail; /* next slot to be written */ + status_message *sml_messages; /* array of messages */ +} sm_list; +#define NUM_REPL_MESSAGES 20 /* max # of messages to save */ + +/* Selective attribute Inclusion states. ORDERING IS SIGNIFICANT */ +#define IMPLICITLY_INCLUDED 1 +#define IMPLICITLY_EXCLUDED 2 +#define EXPLICITLY_EXCLUDED 3 +#define EXPLICITLY_INCLUDED 4 + +#if defined(__JCMREPL_FILTER__) +/* + * Structure used to implement selective attribute filtering. + * sa_filter nodes are arranged in a linked list. + */ +typedef struct _sa_filter { + Slapi_Filter *sa_filter; /* Filter to apply */ + int sa_isexclude; /* non-zero if list is exclude list */ + char **sa_attrlist; /* array - attrs to replicate */ + struct _sa_filter *sa_next; /* Link to next struct */ +} sa_filter; +#endif + +typedef unsigned long changeNumber; +#define a2changeNumber( a ) strtoul(( a ), (char **)NULL, 10 ) + +#define AUTH_SIMPLE 1 +#define AUTH_KERBEROS 2 + +typedef struct modinfo { + char *type; + char *value; + int len; +} modinfo; + +/* + * Representation of one change entry from the replog file. + */ +typedef struct repl { + char *time; /* time of modification */ + changeNumber change; /* number of this change */ + char *dn; /* dn of entry being modified - normalized */ + char *raw_dn; /* dn of entry - not normalized */ + int changetype; /* type of change */ + modinfo *mods; /* modifications to make */ + char *newrdn; /* new rdn for modrdn */ + int deleteoldrdn; /* flag for modrdn */ + +} repl; + +#define BIND_OK 0 +#define BIND_ERR_BADLDP 1 +#define BIND_ERR_OPEN 2 +#define BIND_ERR_BAD_ATYPE 3 +#define BIND_ERR_SIMPLE_FAILED 4 +#define BIND_ERR_KERBEROS_FAILED 5 +#define BIND_ERR_SSL_INIT_FAILED 6 +#define BIND_ERR_RACE 7 + +#define MAX_CHANGENUMBER ULONG_MAX + +#define REPLICATION_SUBSYSTEM "replication" +#define REPL_LDAP_TIMEOUT 30L /* Wait 30 seconds for responses */ + +/* Update the copiedFrom attribute every <n> updates */ +#define UPDATE_COPIEDFROM_INTERVAL 10 +#define REPL_ERROR_REPL_HALTED "REPLICATION HALTED" +#define ATTR_NETSCAPEMDSUFFIX "netscapemdsuffix" + +#define CONFIG_LEGACY_REPLICATIONDN_ATTRIBUTE "nsslapd-legacy-updatedn" +#define CONFIG_LEGACY_REPLICATIONPW_ATTRIBUTE "nsslapd-legacy-updatepw" + +#define LDAP_CONTROL_REPL_MODRDN_EXTRAMODS "2.16.840.1.113730.3.4.999" + +/* Operation types */ +#define OP_MODIFY 1 +#define OP_ADD 2 +#define OP_DELETE 3 +#define OP_MODDN 4 +#define OP_SEARCH 5 +#define OP_COMPARE 6 + +/* 4.0-style housekeeping interval */ +#define REPLICATION_HOUSEKEEPING_INTERVAL (30 * 1000) /* 30 seconds */ + +/* Top of tree for replication configuration information */ +#define REPL_CONFIG_TOP "cn=replication,cn=config" + +/* Functions */ + +/* repl_rootdse.c */ +int repl_rootdse_init(); + +/* In repl.c */ +Slapi_Entry *get_changerecord(const chglog4Info *cl4, changeNumber cnum, int *err); +changeNumber replog_get_firstchangenum(const chglog4Info *cl4, int *err); +changeNumber replog_get_lastchangenum(const chglog4Info *cl4, int *err); +void changelog_housekeeping(time_t cur_time ); + +/* In repl_config.c */ +int repl_config_init (); + +/* Legacy Plugin Functions */ + +int legacy_preop_bind( Slapi_PBlock *pb ); +int legacy_bepreop_bind( Slapi_PBlock *pb ); +int legacy_postop_bind( Slapi_PBlock *pb ); +int legacy_preop_add( Slapi_PBlock *pb ); +int legacy_bepreop_add( Slapi_PBlock *pb ); +int legacy_postop_add( Slapi_PBlock *pb ); +int legacy_preop_modify( Slapi_PBlock *pb ); +int legacy_bepreop_modify( Slapi_PBlock *pb ); +int legacy_postop_modify( Slapi_PBlock *pb ); +int legacy_preop_modrdn( Slapi_PBlock *pb ); +int legacy_bepreop_modrdn( Slapi_PBlock *pb ); +int legacy_postop_modrdn( Slapi_PBlock *pb ); +int legacy_preop_delete( Slapi_PBlock *pb ); +int legacy_bepreop_delete( Slapi_PBlock *pb ); +int legacy_postop_delete( Slapi_PBlock *pb ); +int legacy_preop_search( Slapi_PBlock *pb ); +int legacy_preop_compare( Slapi_PBlock *pb ); +int legacy_pre_entry( Slapi_PBlock *pb ); +int legacy_bepostop_assignchangenum( Slapi_PBlock *pb ); + +int replication_plugin_start( Slapi_PBlock *pb ); +int replication_plugin_poststart( Slapi_PBlock *pb ); +int replication_plugin_stop( Slapi_PBlock *pb ); + +/* In repl.c */ +void replog( Slapi_PBlock *pb, int optype ); +void init_changelog_trimming( changeNumber max_changes, time_t max_age ); + +/* From repl_globals.c */ + +extern char *attr_changenumber; +extern char *attr_targetdn; +extern char *attr_changetype; +extern char *attr_newrdn; +extern char *attr_deleteoldrdn; +extern char *attr_changes; +extern char *attr_newsuperior; +extern char *attr_changetime; +extern char *attr_dataversion; +extern char *attr_csn; + +extern char *changetype_add; +extern char *changetype_delete; +extern char *changetype_modify; +extern char *changetype_modrdn; +extern char *changetype_moddn; + +extern char *type_copyingFrom; +extern char *type_copiedFrom; +extern char *filter_copyingFrom; +extern char *filter_copiedFrom; +extern char *filter_objectclass; + +extern char *type_cn; +extern char *type_objectclass; + +/* JCMREPL - IFP should be defined centrally */ + +#ifndef _IFP +#define _IFP +typedef int (*IFP)(); +#endif + +/* In cl4.c */ + +changeNumber ldapi_assign_changenumber(chglog4Info *cl4); +changeNumber ldapi_get_last_changenumber(chglog4Info *cl4); +changeNumber ldapi_get_first_changenumber(chglog4Info *cl4); +void ldapi_commit_changenumber(chglog4Info *cl4, changeNumber cnum); +void ldapi_set_first_changenumber(chglog4Info *cl4, changeNumber cnum); +void ldapi_set_last_changenumber(chglog4Info *cl4, changeNumber cnum); +void ldapi_initialize_changenumbers(chglog4Info *cl4, changeNumber first, changeNumber last); + +#define LDBM_TYPE "ldbm" +#define CHANGELOG_LDBM_TYPE "changelog-ldbm" + +#define MAX_RETRY_INTERVAL 3600 /* sec = 1 hour */ + +#define REPL_PROTOCOL_UNKNOWN 0 +#define REPL_PROTOCOL_40 1 +#define REPL_PROTOCOL_50_INCREMENTAL 2 +#define REPL_PROTOCOL_50_TOTALUPDATE 3 + +/* In repl_globals.c */ +int decrement_repl_active_threads(); +int increment_repl_active_threads(); + +/* operation extensions */ + +/* Type of extensions that can be registered */ +typedef enum +{ + REPL_SUP_EXT_OP, /* extension for Operation object, replication supplier */ + REPL_SUP_EXT_CONN, /* extension for Connection object, replication supplier */ + REPL_CON_EXT_OP, /* extension for Operation object, replication consumer */ + REPL_CON_EXT_CONN, /* extension for Connection object, replication consumer */ + REPL_CON_EXT_MTNODE,/* extension for mapping_tree_node object, replication consumer */ + REPL_EXT_ALL +} ext_type; + +/* general extension functions - repl_ext.c */ +void repl_sup_init_ext (); /* initializes registrations - must be called first */ +void repl_con_init_ext (); /* initializes registrations - must be called first */ +int repl_sup_register_ext (ext_type type); /* registers an extension of the specified type */ +int repl_con_register_ext (ext_type type); /* registers an extension of the specified type */ +void* repl_sup_get_ext (ext_type type, void *object); /* retireves the extension from the object */ +void* repl_con_get_ext (ext_type type, void *object); /* retireves the extension from the object */ + +/* Operation extension functions - supplier_operation_extension.c */ + +/* --- supplier operation extension --- */ +typedef struct supplier_operation_extension +{ + int prevent_recursive_call; + struct slapi_operation_parameters *operation_parameters; + char *repl_gen; +} supplier_operation_extension; + +/* extension construct/destructor */ +void* supplier_operation_extension_constructor (void *object, void *parent); +void supplier_operation_extension_destructor (void* ext,void *object, void *parent); + +/* --- consumer operation extension --- */ +typedef struct consumer_operation_extension +{ + int has_cf; /* non-zero if the operation contains a copiedFrom/copyingFrom attr */ + void *search_referrals; +} consumer_operation_extension; + +/* extension construct/destructor */ +void* consumer_operation_extension_constructor (void *object, void *parent); +void consumer_operation_extension_destructor (void* ext,void *object, void *parent); + +/* Connection extension functions - repl_connext.c */ + +/* --- connection extension --- */ +/* ONREPL - some pointers are void* because they represent 5.0 data structures + not known in this header. Fix */ +typedef struct consumer_connection_extension +{ + int is_legacy_replication_dn; + int repl_protocol_version; /* the replication protocol version number the supplier is talking. */ + void *replica_acquired; /* Object* for replica */ + void *supplier_ruv; /* RUV* */ + int isreplicationsession; + Slapi_Connection *connection; +} consumer_connection_extension; + +/* extension construct/destructor */ +void* consumer_connection_extension_constructor (void *object,void *parent); +void consumer_connection_extension_destructor (void* ext,void *object,void *parent); + +/* mapping tree extension - stores replica object */ +typedef struct multimaster_mtnode_extension +{ + Object *replica; +} multimaster_mtnode_extension; +void* multimaster_mtnode_extension_constructor (void *object,void *parent); +void multimaster_mtnode_extension_destructor (void* ext,void *object,void *parent); + +/* In repl_init.c */ + +int get_legacy_stop(); + +/* In repl_entry.c */ +void repl_entry_init(int argc, char** argv); + +/* In repl_ops.c */ +int legacy_preop( Slapi_PBlock *pb, const char* caller, int operation_type); +int legacy_postop( Slapi_PBlock *pb, const char* caller, int operation_type); + +/* In profile.c */ + +#ifdef PROFILE +#define PROFILE_POINT if (CFG_profile) profile_log(__FILE__,__LINE__) /* JCMREPL - Where is the profiling flag stored? */ +#else +#define PROFILE_POINT ((void)0) +#endif + +void profile_log(char *file,int line); +void profile_open(); +void profile_close(); + +/* in repl_controls.c */ +void add_repl_control_mods( Slapi_PBlock *pb, Slapi_Mods *smods ); + +/* ... */ +void create_entity (char* DN, const char* oclass); + +void write_replog_db( int optype, char *dn, void *change, int flag, changeNumber changenum, time_t curtime, LDAPMod **modrdn_mods ); +int entry2reple( Slapi_Entry *e, Slapi_Entry *oe ); +int mods2reple( Slapi_Entry *e, LDAPMod **ldm ); +int modrdn2reple( Slapi_Entry *e, char *newrdn, int deloldrdn, LDAPMod **ldm ); + +/* In legacy_consumer.c */ +void process_legacy_cf(Slapi_PBlock *pb); +int legacy_consumer_is_replicationdn(char *dn); +int legacy_consumer_is_replicationpw(struct berval *creds); +int legacy_consumer_config_init(); + +/* function that gets called when a backend state is changed */ +void legacy_consumer_be_state_change (void *handle, char *be_name, + int old_be_state, int new_be_state); + +#endif /* _REPL_H_ */ + + + diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h new file mode 100644 index 00000000..d936cbea --- /dev/null +++ b/ldap/servers/plugins/replication/repl5.h @@ -0,0 +1,480 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5.h - 5.0 replication header */ + +#ifndef _REPL5_H_ +#define _REPL5_H_ + +#include <limits.h> +#include <time.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <sys/param.h> +#endif /* _WIN32 */ + +#include "portable.h" /* GGOODREPL - is this cheating? */ +#include "repl_shared.h" +#include "llist.h" +#include "repl5_ruv.h" +#include "cl4.h" + +/* DS 5.0 replication protocol OIDs */ +#define REPL_START_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.3" +#define REPL_END_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.5" +#define REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID "2.16.840.1.113730.3.5.6" +#define REPL_NSDS50_REPLICATION_RESPONSE_OID "2.16.840.1.113730.3.5.4" +#define REPL_NSDS50_UPDATE_INFO_CONTROL_OID "2.16.840.1.113730.3.4.13" +#define REPL_NSDS50_INCREMENTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.1" +#define REPL_NSDS50_TOTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.2" + +/* DS 5.0 replication protocol error codes */ +#define NSDS50_REPL_REPLICA_READY 0x00 /* Replica ready, go ahead */ +#define NSDS50_REPL_REPLICA_BUSY 0x01 /* Replica busy, try later */ +#define NSDS50_REPL_EXCESSIVE_CLOCK_SKEW 0x02 /* Supplier clock too far ahead */ +#define NSDS50_REPL_PERMISSION_DENIED 0x03 /* Bind DN not allowed to send updates */ +#define NSDS50_REPL_DECODING_ERROR 0x04 /* Consumer couldn't decode extended operation */ +#define NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL 0x05 /* Consumer doesn't understand suplier's update protocol */ +#define NSDS50_REPL_NO_SUCH_REPLICA 0x06 /* Consumer holds no such replica */ +#define NSDS50_REPL_BELOW_PURGEPOINT 0x07 /* Supplier provided a CSN below the consumer's purge point */ +#define NSDS50_REPL_INTERNAL_ERROR 0x08 /* Something bad happened on consumer */ +#define NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED 0x09 /* Replica released successfully */ +#define NSDS50_REPL_LEGACY_CONSUMER 0x0A /* replica is a legacy consumer */ +#define NSDS50_REPL_REPLICAID_ERROR 0x0B /* replicaID doesn't seem to be unique */ +#define NSDS50_REPL_DISABLED 0x0C /* replica suffix is disabled */ +#define NSDS50_REPL_UPTODATE 0x0D /* replica is uptodate */ +#define NSDS50_REPL_REPLICA_NO_RESPONSE 0xff /* No response received */ + +/* Protocol status */ +#define PROTOCOL_STATUS_UNKNOWN 701 +#define PROTOCOL_STATUS_INCREMENTAL_AWAITING_CHANGES 702 +#define PROTOCOL_STATUS_INCREMENTAL_ACQUIRING_REPLICA 703 +#define PROTOCOL_STATUS_INCREMENTAL_RELEASING_REPLICA 704 +#define PROTOCOL_STATUS_INCREMENTAL_SENDING_UPDATES 705 +#define PROTOCOL_STATUS_INCREMENTAL_BACKING_OFF 706 +#define PROTOCOL_STATUS_INCREMENTAL_NEEDS_TOTAL_UPDATE 707 +#define PROTOCOL_STATUS_INCREMENTAL_FATAL_ERROR 708 +#define PROTOCOL_STATUS_TOTAL_ACQUIRING_REPLICA 709 +#define PROTOCOL_STATUS_TOTAL_RELEASING_REPLICA 710 +#define PROTOCOL_STATUS_TOTAL_SENDING_DATA 711 + +/* To Allow Consumer Initialisation when adding an agreement - */ +#define STATE_PERFORMING_TOTAL_UPDATE 501 +#define STATE_PERFORMING_INCREMENTAL_UPDATE 502 + +#define MAX_NUM_OF_MASTERS 64 +#define REPL_SESSION_ID_SIZE 64 + +/* Attribute names for replication agreement attributes */ +extern const char *type_nsds5ReplicaHost; +extern const char *type_nsds5ReplicaPort; +extern const char *type_nsds5TransportInfo; +extern const char *type_nsds5ReplicaBindDN; +extern const char *type_nsds5ReplicaCredentials; +extern const char *type_nsds5ReplicaBindMethod; +extern const char *type_nsds5ReplicaRoot; +extern const char *type_nsds5ReplicatedAttributeList; +extern const char *type_nsds5ReplicaUpdateSchedule; +extern const char *type_nsds5ReplicaInitialize; +extern const char *type_nsds5ReplicaTimeout; +extern const char *type_nsds5ReplicaBusyWaitTime; +extern const char *type_nsds5ReplicaSessionPauseTime; + +/* To Allow Consumer Initialisation when adding an agreement - */ +extern const char *type_nsds5BeginReplicaRefresh; + +/* replica related attributes */ +extern const char *attr_replicaId; +extern const char *attr_replicaRoot; +extern const char *attr_replicaType; +extern const char *attr_replicaBindDn; +extern const char *attr_state; +extern const char *attr_flags; +extern const char *attr_replicaName; +extern const char *attr_replicaReferral; +extern const char *type_ruvElement; +extern const char *type_replicaPurgeDelay; +extern const char *type_replicaChangeCount; +extern const char *type_replicaTombstonePurgeInterval; +extern const char *type_replicaLegacyConsumer; +extern const char *type_ruvElementUpdatetime; + +/* multimaster plugin points */ +int multimaster_preop_bind (Slapi_PBlock *pb); +int multimaster_preop_add (Slapi_PBlock *pb); +int multimaster_preop_delete (Slapi_PBlock *pb); +int multimaster_preop_modify (Slapi_PBlock *pb); +int multimaster_preop_modrdn (Slapi_PBlock *pb); +int multimaster_preop_search (Slapi_PBlock *pb); +int multimaster_preop_compare (Slapi_PBlock *pb); +int multimaster_bepreop_add (Slapi_PBlock *pb); +int multimaster_bepreop_delete (Slapi_PBlock *pb); +int multimaster_bepreop_modify (Slapi_PBlock *pb); +int multimaster_bepreop_modrdn (Slapi_PBlock *pb); +int multimaster_bepostop_modrdn (Slapi_PBlock *pb); +int multimaster_bepostop_delete (Slapi_PBlock *pb); +int multimaster_postop_bind (Slapi_PBlock *pb); +int multimaster_postop_add (Slapi_PBlock *pb); +int multimaster_postop_delete (Slapi_PBlock *pb); +int multimaster_postop_modify (Slapi_PBlock *pb); +int multimaster_postop_modrdn (Slapi_PBlock *pb); + +/* In repl5_init.c */ +char* get_thread_private_agmtname (); +void set_thread_private_agmtname (const char *agmtname); +void* get_thread_private_cache (); +void set_thread_private_cache (void *buf); +char* get_repl_session_id (Slapi_PBlock *pb, char *id, CSN **opcsn); + +/* In repl_extop.c */ +int multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb); +int multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb); +int extop_noop(Slapi_PBlock *pb); +struct berval *NSDS50StartReplicationRequest_new(const char *protocol_oid, + const char *repl_root, char **extra_referrals, CSN *csn); +struct berval *NSDS50EndReplicationRequest_new(char *repl_root); +int decode_repl_ext_response(struct berval *data, int *response_code, + struct berval ***ruv_bervals); + +/* In repl5_total.c */ +int multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb); + +/* In repl_controls.c */ +int create_NSDS50ReplUpdateInfoControl(const char *uuid, + const char *superior_uuid, const CSN *csn, + LDAPMod **modify_mods, LDAPControl **ctrlp); +void destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp); +int decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp, + char **uuid, char **newsuperior_uuid, CSN **csn, LDAPMod ***modrdn_mods); + +/* In repl5_replsupplier.c */ +typedef struct repl_supplier Repl_Supplier; +Repl_Supplier *replsupplier_init(Slapi_Entry *e); +void replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb); +void replsupplier_start(Repl_Supplier *rs); +void replsupplier_stop(Repl_Supplier *rs); +void replsupplier_destroy(Repl_Supplier **rs); +void replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask); +PRUint32 replsupplier_get_status(Repl_Supplier *rs); + +/* In repl5_plugins.c */ +int multimaster_set_local_purl(); +const char *multimaster_get_local_purl(); +PRBool multimaster_started(); + +/* In repl5_schedule.c */ +typedef struct schedule Schedule; +typedef void (*window_state_change_callback)(void *arg, PRBool opened); +Schedule *schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id); +void schedule_destroy(Schedule *s); +int schedule_set(Schedule *sch, Slapi_Attr *attr); +char **schedule_get(Schedule *sch); +int schedule_in_window_now(Schedule *sch); +PRTime schedule_next(Schedule *sch); +int schedule_notify(Schedule *sch, Slapi_PBlock *pb); +void schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule); +void schedule_set_startup_delay(Schedule *sch, size_t startup_delay); +void schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog); +void schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int flags); +#define REPLICATION_SESSION_SUCCESS 0 + +/* In repl5_bos.c */ +typedef struct repl_bos Repl_Bos; + +/* In repl5_agmt.c */ +typedef struct repl5agmt Repl_Agmt; +#define TRANSPORT_FLAG_SSL 1 +#define TRANSPORT_FLAG_TLS 2 +#define BINDMETHOD_SIMPLE_AUTH 1 +#define BINDMETHOD_SSL_CLIENTAUTH 2 +Repl_Agmt *agmt_new_from_entry(Slapi_Entry *e); +Repl_Agmt *agmt_new_from_pblock(Slapi_PBlock *pb); +void agmt_delete(void **ra); +const Slapi_DN *agmt_get_dn_byref(const Repl_Agmt *ra); +int agmt_get_auto_initialize(const Repl_Agmt *ra); +long agmt_get_timeout(const Repl_Agmt *ra); +long agmt_get_busywaittime(const Repl_Agmt *ra); +long agmt_get_pausetime(const Repl_Agmt *ra); +int agmt_start(Repl_Agmt *ra); +int agmt_stop(Repl_Agmt *ra); +int agmt_replicate_now(Repl_Agmt *ra); +char *agmt_get_hostname(const Repl_Agmt *ra); +int agmt_get_port(const Repl_Agmt *ra); +PRUint32 agmt_get_transport_flags(const Repl_Agmt *ra); +char *agmt_get_binddn(const Repl_Agmt *ra); +struct berval *agmt_get_credentials(const Repl_Agmt *ra); +int agmt_get_bindmethod(const Repl_Agmt *ra); +Slapi_DN *agmt_get_replarea(const Repl_Agmt *ra); +int agmt_is_fractional(const Repl_Agmt *ra); +int agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname); +int agmt_is_50_mm_protocol(const Repl_Agmt *ra); +int agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name); +int agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name); +int agmt_schedule_in_window_now(const Repl_Agmt *ra); +int agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_timeout_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_busywaittime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_pausetime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_credentials_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_binddn_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_bind_method_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +int agmt_set_transportinfo_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); +const char *agmt_get_long_name(const Repl_Agmt *ra); +int agmt_initialize_replica(const Repl_Agmt *agmt); +void agmt_replica_init_done (const Repl_Agmt *agmt); +void agmt_notify_change(Repl_Agmt *ra, Slapi_PBlock *pb); +Object* agmt_get_consumer_ruv (Repl_Agmt *ra); +ReplicaId agmt_get_consumer_rid ( Repl_Agmt *ra, void *conn ); +int agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv); +void agmt_update_consumer_ruv (Repl_Agmt *ra); +CSN* agmt_get_consumer_schema_csn (Repl_Agmt *ra); +void agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn); +void agmt_set_last_update_in_progress (Repl_Agmt *ra, PRBool in_progress); +void agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time); +void agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time); +void agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg); +void agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress); +void agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time); +void agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time); +void agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg); +void agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped); +void agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize); + +typedef struct replica Replica; + +/* In repl5_agmtlist.c */ +int agmtlist_config_init(); +void agmtlist_shutdown(); +void agmtlist_notify_all(Slapi_PBlock *pb); +Object* agmtlist_get_first_agreement_for_replica (Replica *r); +Object* agmtlist_get_next_agreement_for_replica (Replica *r, Object *prev); + + +/* In repl5_backoff.c */ +typedef struct backoff_timer Backoff_Timer; +#define BACKOFF_FIXED 1 +#define BACKOFF_EXPONENTIAL 2 +#define BACKOFF_RANDOM 3 +Backoff_Timer *backoff_new(int timer_type, int initial_interval, int max_interval); +time_t backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data); +time_t backoff_step(Backoff_Timer *bt); +int backoff_expired(Backoff_Timer *bt, int margin); +void backoff_delete(Backoff_Timer **btp); + +/* In repl5_connection.c */ +typedef struct repl_connection Repl_Connection; +typedef enum +{ + CONN_OPERATION_SUCCESS, + CONN_OPERATION_FAILED, + CONN_NOT_CONNECTED, + CONN_SUPPORTS_DS5_REPL, + CONN_DOES_NOT_SUPPORT_DS5_REPL, + CONN_SCHEMA_UPDATED, + CONN_SCHEMA_NO_UPDATE_NEEDED, + CONN_LOCAL_ERROR, + CONN_BUSY, + CONN_SSL_NOT_ENABLED, + CONN_TIMEOUT +} ConnResult; +Repl_Connection *conn_new(Repl_Agmt *agmt); +ConnResult conn_connect(Repl_Connection *conn); +void conn_disconnect(Repl_Connection *conn); +void conn_delete(Repl_Connection *conn); +void conn_get_error(Repl_Connection *conn, int *operation, int *error); +ConnResult conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_delete(Repl_Connection *conn, const char *dn, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_rename(Repl_Connection *conn, const char *dn, + const char *newrdn, const char *newparent, int deleteoldrdn, + LDAPControl *update_control, LDAPControl ***returned_controls); +ConnResult conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid, + struct berval *payload, char **retoidp, struct berval **retdatap, + LDAPControl *update_control, LDAPControl ***returned_controls); +const char *conn_get_status(Repl_Connection *conn); +void conn_start_linger(Repl_Connection *conn); +void conn_cancel_linger(Repl_Connection *conn); +ConnResult conn_replica_supports_ds5_repl(Repl_Connection *conn); +ConnResult conn_read_entry_attribute(Repl_Connection *conn, const char *dn, char *type, + struct berval ***returned_bvals); +ConnResult conn_push_schema(Repl_Connection *conn, CSN **remotecsn); +void conn_set_timeout(Repl_Connection *conn, long timeout); +void conn_set_agmt_changed(Repl_Connection *conn); + +/* In repl5_protocol.c */ +typedef struct repl_protocol Repl_Protocol; +Repl_Protocol *prot_new(Repl_Agmt *agmt, int protocol_state); +void prot_start(Repl_Protocol *rp); +Repl_Agmt *prot_get_agreement(Repl_Protocol *rp); +/* initiate total protocol */ +void prot_initialize_replica(Repl_Protocol *rp); +/* stop protocol session in progress */ +void prot_stop(Repl_Protocol *rp); +void prot_delete(Repl_Protocol **rpp); +void prot_free(Repl_Protocol **rpp); +PRBool prot_set_active_protocol (Repl_Protocol *rp, PRBool total); +void prot_clear_active_protocol (Repl_Protocol *rp); +Repl_Connection *prot_get_connection(Repl_Protocol *rp); +void prot_resume(Repl_Protocol *rp, int wakeup_action); +void prot_notify_update(Repl_Protocol *rp); +void prot_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name); +void prot_notify_window_opened (Repl_Protocol *rp); +void prot_notify_window_closed (Repl_Protocol *rp); +Object *prot_get_replica_object(Repl_Protocol *rp); +void prot_replicate_now(Repl_Protocol *rp); + +/* In repl5_replica.c */ +typedef enum +{ + REPLICA_TYPE_UNKNOWN, + REPLICA_TYPE_PRIMARY, + REPLICA_TYPE_READONLY, + REPLICA_TYPE_UPDATABLE, + REPLICA_TYPE_END +} ReplicaType; + +#define RUV_STORAGE_ENTRY_UNIQUEID "ffffffff-ffffffff-ffffffff-ffffffff" +#define START_ITERATION_ENTRY_UNIQUEID "00000000-00000000-00000000-00000000" +#define START_ITERATION_ENTRY_DN "cn=start iteration" + +typedef int (*FNEnumReplica) (Replica *r, void *arg); + +/* this function should be called to construct the replica object + from the data already in the DIT */ +Replica *replica_new(const Slapi_DN *root); +/* this function should be called to construct the replica object + during addition of the replica over LDAP */ +Replica *replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation); +void replica_destroy(void **arg); +PRBool replica_get_exclusive_access(Replica *r, PRBool *isInc, int connid, int opid, + const char *locking_purl, + char **current_purl); +void replica_relinquish_exclusive_access(Replica *r, int connid, int opid); +PRBool replica_get_tombstone_reap_active(const Replica *r); +const Slapi_DN *replica_get_root(const Replica *r); +const char *replica_get_name(const Replica *r); +ReplicaId replica_get_rid (const Replica *r); +void replica_set_rid (Replica *r, ReplicaId rid); +PRBool replica_is_initialized (const Replica *r); +Object *replica_get_ruv (const Replica *r); +/* replica now owns the RUV */ +void replica_set_ruv (Replica *r, RUV *ruv); +Object *replica_get_csngen (const Replica *r); +ReplicaType replica_get_type (const Replica *r); +void replica_set_type (Replica *r, ReplicaType type); +PRBool replica_is_legacy_consumer (const Replica *r); +void replica_set_legacy_consumer (Replica *r, PRBool legacy); +char *replica_get_legacy_purl (const Replica *r); +void replica_set_legacy_purl (Replica *r, const char *purl); +PRBool replica_is_updatedn (const Replica *r, const Slapi_DN *sdn); +void replica_set_updatedn (Replica *r, const Slapi_ValueSet *vs, int mod_op); +char *replica_get_generation (const Replica *r); +/* currently supported flags */ +#define REPLICA_LOG_CHANGES 0x1 /* enable change logging */ +PRBool replica_is_flag_set (const Replica *r, PRUint32 flag); +void replica_set_flag (Replica *r, PRUint32 flag, PRBool clear); +void replica_replace_flags (Replica *r, PRUint32 flags); +void replica_dump(Replica *r); +void replica_set_enabled (Replica *r, PRBool enable); +Object *replica_get_replica_from_dn (const Slapi_DN *dn); +void replica_update_ruv(Replica *replica, const CSN *csn, const char *replica_purl); +Object *replica_get_replica_for_op (Slapi_PBlock *pb); +/* the functions below manipulate replica hash */ +int replica_init_name_hash (); +void replica_destroy_name_hash (); +int replica_add_by_name (const char *name, Object *replica); +int replica_delete_by_name (const char *name); +Object* replica_get_by_name (const char *name); +void replica_flush(Replica *r); +void replica_get_referrals(const Replica *r, char ***referrals); +void replica_set_referrals(Replica *r,const Slapi_ValueSet *vs); +int replica_update_csngen_state (Replica *r, const RUV *ruv); +CSN *replica_get_purge_csn(const Replica *r); +int replica_log_ruv_elements (const Replica *r); +void replica_enumerate_replicas (FNEnumReplica fn, void *arg); +int replica_reload_ruv (Replica *r); +int replica_check_for_data_reload (Replica *r, void *arg); +/* the functions below manipulate replica dn hash */ +int replica_init_dn_hash (); +void replica_destroy_dn_hash (); +int replica_add_by_dn (const char *dn); +int replica_delete_by_dn (const char *dn); +int replica_is_being_configured (const char *dn); +const CSN * _get_deletion_csn(Slapi_Entry *e); +int legacy_consumer_init_referrals (Replica *r); +void consumer5_set_mapping_tree_state_for_replica(const Replica *r, RUV *supplierRuv); +Object *replica_get_for_backend (const char *be_name); +void replica_set_purge_delay (Replica *r, PRUint32 purge_delay); +void replica_set_tombstone_reap_interval (Replica *r, long interval); +void replica_update_ruv_consumer (Replica *r, RUV *supplier_ruv); +void replica_set_ruv_dirty (Replica *r); +void replica_write_ruv (Replica *r); +/* The functions below handles the state flag */ +/* Current internal state flags */ +/* The replica can be busy and not other flag, + * it means that the protocol has ended, but the work is not done yet. + * It happens on total protocol, the end protocol has been received, + * and the thread waits for import to finish + */ +#define REPLICA_IN_USE 1 /* The replica is busy */ +#define REPLICA_INCREMENTAL_IN_PROGRESS 2 /* Set only between start and stop inc */ +#define REPLICA_TOTAL_IN_PROGRESS 4 /* Set only between start and stop total */ +#define REPLICA_AGREEMENTS_DISABLED 8 /* Replica is offline */ +PRBool replica_is_state_flag_set(Replica *r, PRInt32 flag); +void replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear); +void replica_enable_replication (Replica *r); +void replica_disable_replication (Replica *r, Object *r_obj); +int replica_start_agreement(Replica *r, Repl_Agmt *ra); + +CSN* replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn ); +int replica_get_attr ( Slapi_PBlock *pb, const char *type, void *value ); + +/* mapping tree extensions manipulation */ +void multimaster_mtnode_extension_init (); +void multimaster_mtnode_extension_destroy (); +void multimaster_mtnode_construct_replicas (); + +void multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state); + +/* In repl5_replica_config.c */ +int replica_config_init(); +void replica_config_destroy (); + +/* replutil.c */ +LDAPControl* create_managedsait_control (); +LDAPControl* create_backend_control(Slapi_DN *sdn); +void repl_set_mtn_state_and_referrals(const Slapi_DN *sdn, const char *mtn_state, + const RUV *ruv, char **ruv_referrals, + char **other_referrals); +void repl_set_repl_plugin_path(const char *path); + +/* repl5_updatedn_list.c */ +typedef void *ReplicaUpdateDNList; +typedef int (*FNEnumDN)(Slapi_DN *dn, void *arg); +ReplicaUpdateDNList replica_updatedn_list_new(const Slapi_Entry *entry); +void replica_updatedn_list_free(ReplicaUpdateDNList list); +void replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs); +void replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs); +void replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs); +PRBool replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn); +char *replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter); +void replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg); + +/* enabling developper traces for MMR to understand the total/inc protocol state machines */ +#ifdef DEV_DEBUG +#define SLAPI_LOG_DEV_DEBUG SLAPI_LOG_FATAL +#define dev_debug(a) slapi_log_error(SLAPI_LOG_DEV_DEBUG, "DEV_DEBUG", "%s\n", a) +#else +#define dev_debug(a) +#endif + +void repl5_set_debug_timeout(const char *val); + +#endif /* _REPL5_H_ */ diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c new file mode 100644 index 00000000..2992fc11 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_agmt.c @@ -0,0 +1,1766 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_agmt.c */ +/* + + Support for 5.0-style replication agreements. + + Directory Server 5.0 replication agreements contain information about + replication consumers that we are supplying. + + This module encapsulates the methods available for adding, deleting, + modifying, and firing replication agreements. + + Methods: + + agmt_new - Create a new replication agreement, in response to a new + replication agreement being added over LDAP. + agmt_delete - Destroy an agreement. It is an error to destroy an + agreement that has not been stopped. + agmt_getstatus - get the status of this replication agreement. + agmt_replicate_now - initiate a replication session asap, even if the + schedule says we shouldn't. + agmt_start - start replicating, according to schedule. Starts a new + thread to handle replication. + agmt_stop - stop replicating asap and end replication thread. + agmt_notify_change - notify the replication agreement about a change that + has been logged. The replication agreement will + decide if it needs to take some action, e.g. start a + replication session. + agmt_initialize_replica - start a complete replica refresh. + agmt_set_schedule_from_entry - (re)set the schedule associated with this + replication agreement based on a RA entry's contents. + agmt_set_credentials_from_entry - (re)set the credentials used to bind + to the remote replica. + agmt_set_binddn_from_entry - (re)set the DN used to bind + to the remote replica. + agmt_set_bind_method_from_entry - (re)set the bind method used to bind + to the remote replica (SIMPLE or SSLCLIENTAUTH). + agmt_set_transportinfo_from_entry - (re)set the transport used to bind + to the remote replica (SSL or not) + +*/ + +#include "repl5.h" +#include "repl5_prot_private.h" +#include "cl5_api.h" + +#define DEFAULT_TIMEOUT 600 /* (seconds) default outbound LDAP connection */ +#define TRANSPORT_FLAG_SSL 1 +#define STATUS_LEN 1024 + +struct changecounter { + ReplicaId rid; + PRUint32 num_replayed; + PRUint32 num_skipped; +}; + +typedef struct repl5agmt { + char *hostname; /* remote hostname */ + int port; /* port of remote server */ + PRUint32 transport_flags; /* SSL, TLS, etc. */ + char *binddn; /* DN to bind as */ + struct berval *creds; /* Password, or certificate */ + int bindmethod; /* Bind method - simple, SSL */ + Slapi_DN *replarea; /* DN of replicated area */ + char **frac_attrs; /* list of fractional attributes to be replicated */ + Schedule *schedule; /* Scheduling information */ + int auto_initialize; /* 1 = automatically re-initialize replica */ + const Slapi_DN *dn; /* DN of replication agreement entry */ + const Slapi_RDN *rdn; /* RDN of replication agreement entry */ + char *long_name; /* Long name (rdn + host, port) of entry, for logging */ + Repl_Protocol *protocol; /* Protocol object - manages protocol */ + struct changecounter *changecounters[MAX_NUM_OF_MASTERS]; /* changes sent/skipped since server start up */ + int num_changecounters; + time_t last_update_start_time; /* Local start time of last update session */ + time_t last_update_end_time; /* Local end time of last update session */ + char last_update_status[STATUS_LEN]; /* Status of last update. Format = numeric code <space> textual description */ + PRBool update_in_progress; + time_t last_init_start_time; /* Local start time of last total init */ + time_t last_init_end_time; /* Local end time of last total init */ + char last_init_status[STATUS_LEN]; /* Status of last total init. Format = numeric code <space> textual description */ + PRLock *lock; + Object *consumerRUV; /* last RUV received from the consumer - used for changelog purging */ + CSN *consumerSchemaCSN; /* last schema CSN received from the consumer */ + ReplicaId consumerRID; /* indicates if the consumer is the originator of a CSN */ + long timeout; /* timeout (in seconds) for outbound LDAP connections to remote server */ + PRBool stop_in_progress; /* set by agmt_stop when shutting down */ + long busywaittime; /* time in seconds to wait after getting a REPLICA BUSY from the consumer - + to allow another supplier to finish sending its updates - + if set to 0, this means to use the default value if we get a busy + signal from the consumer */ + long pausetime; /* time in seconds to pause after sending updates - + to allow another supplier to send its updates - + should be greater than busywaittime - + if set to 0, this means do not pause */ +} repl5agmt; + +/* Forward declarations */ +void agmt_delete(void **rap); +static void update_window_state_change_callback (void *arg, PRBool opened); +static int get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e, + Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); +static int agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); + +/* +Schema for replication agreement: + +cn +nsds5ReplicaHost - hostname +nsds5ReplicaPort - port number +nsds5ReplicaTransportInfo - "SSL", "startTLS", or may be absent; +nsds5ReplicaBindDN +nsds5ReplicaCredentials +nsds5ReplicaBindMethod - "SIMPLE" or "SSLCLIENTAUTH". +nsds5ReplicaRoot - Replicated suffix +nsds5ReplicatedAttributeList - Unused so far (meant for fractional repl). +nsds5ReplicaUpdateSchedule +nsds5ReplicaTimeout - Outbound repl operations timeout +nsds50ruv - consumer's RUV +nsds5ReplicaBusyWaitTime - time to wait after getting a REPLICA BUSY from the consumer +nsds5ReplicaSessionPauseTime - time to pause after sending updates to allow another supplier to send +*/ + + +/* + * Validate an agreement, making sure that it's valid. + * Return 1 if the agreement is valid, 0 otherwise. + */ +static int +agmt_is_valid(Repl_Agmt *ra) +{ + int return_value = 1; /* assume valid, initially */ + PR_ASSERT(NULL != ra); + PR_ASSERT(NULL != ra->dn); + + if (NULL == ra->hostname) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: missing host name.\n", slapi_sdn_get_dn(ra->dn)); + return_value = 0; + } + if (ra->port <= 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid port number %d.\n", slapi_sdn_get_dn(ra->dn), ra->port); + return_value = 0; + } + if (ra->timeout < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid timeout %d.\n", slapi_sdn_get_dn(ra->dn), ra->timeout); + return_value = 0; + } + if (ra->busywaittime < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid busy wait time %d.\n", slapi_sdn_get_dn(ra->dn), ra->busywaittime); + return_value = 0; + } + if (ra->pausetime < 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Replication agreement \"%s\" " + "is malformed: invalid pausetime %d.\n", slapi_sdn_get_dn(ra->dn), ra->pausetime); + return_value = 0; + } + return return_value; +} + + +Repl_Agmt * +agmt_new_from_entry(Slapi_Entry *e) +{ + Repl_Agmt *ra; + char *tmpstr; + Slapi_Attr *sattr; + + char *auto_initialize = NULL; + char *val_nsds5BeginReplicaRefresh = "start"; + + ra = (Repl_Agmt *)slapi_ch_calloc(1, sizeof(repl5agmt)); + if ((ra->lock = PR_NewLock()) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to create new lock " + "for replication agreement \"%s\" - agreement ignored.\n", + slapi_entry_get_dn_const(e)); + goto loser; + } + + /* Find all the stuff we need for the agreement */ + + /* To Allow Consumer Initialisation when adding an agreement: */ + + /* + Using 'auto_initialize' member of 'repl5agmt' structure to + store the effect of 'nsds5BeginReplicaRefresh' attribute's value + in it. + */ + auto_initialize = slapi_entry_attr_get_charptr(e, type_nsds5BeginReplicaRefresh); + if ((auto_initialize != NULL) && (strcasecmp(auto_initialize, val_nsds5BeginReplicaRefresh) == 0)) + { + ra->auto_initialize = STATE_PERFORMING_TOTAL_UPDATE; + } + else + { + ra->auto_initialize = STATE_PERFORMING_INCREMENTAL_UPDATE; + } + + if (auto_initialize) + { + slapi_ch_free_string (&auto_initialize); + } + + /* Host name of remote replica */ + ra->hostname = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaHost); + /* Port number for remote replica instance */ + ra->port = slapi_entry_attr_get_int(e, type_nsds5ReplicaPort); + /* SSL, TLS, or other transport stuff */ + ra->transport_flags = 0; + agmt_set_transportinfo_no_lock(ra, e); + + /* DN to use when binding. May be empty if cert-based auth is to be used. */ + ra->binddn = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBindDN); + if (NULL == ra->binddn) + { + ra->binddn = slapi_ch_strdup(""); + } + /* Credentials to use when binding. */ + ra->creds = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + ra->creds->bv_val = NULL; + ra->creds->bv_len = 0; + if (slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr) == 0) + { + Slapi_Value *sval; + if (slapi_attr_first_value(sattr, &sval) == 0) + { + const struct berval *bv = slapi_value_get_berval(sval); + if (NULL != bv) + { + ra->creds->bv_val = slapi_ch_malloc(bv->bv_len + 1); + memcpy(ra->creds->bv_val, bv->bv_val, bv->bv_len); + ra->creds->bv_len = bv->bv_len; + ra->creds->bv_val[bv->bv_len] = '\0'; /* be safe */ + } + } + } + /* How to bind */ + (void)agmt_set_bind_method_no_lock(ra, e); + + /* timeout. */ + ra->timeout = DEFAULT_TIMEOUT; + if (slapi_entry_attr_find(e, type_nsds5ReplicaTimeout, &sattr) == 0) + { + Slapi_Value *sval; + if (slapi_attr_first_value(sattr, &sval) == 0) + { + ra->timeout = slapi_value_get_long(sval); + } + } + + /* DN of entry at root of replicated area */ + tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaRoot); + if (NULL != tmpstr) + { + ra->replarea = slapi_sdn_new_dn_passin(tmpstr); + } + /* XXXggood get fractional attribute include/exclude lists here */ + /* Replication schedule */ + ra->schedule = schedule_new(update_window_state_change_callback, ra, agmt_get_long_name(ra)); + if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) == 0) + { + schedule_set(ra->schedule, sattr); + } + + /* busy wait time - time to wait after getting REPLICA BUSY from consumer */ + ra->busywaittime = slapi_entry_attr_get_long(e, type_nsds5ReplicaBusyWaitTime); + + /* pause time - time to pause after a session has ended */ + ra->pausetime = slapi_entry_attr_get_long(e, type_nsds5ReplicaSessionPauseTime); + + /* consumer's RUV */ + if (slapi_entry_attr_find(e, type_ruvElement, &sattr) == 0) + { + RUV *ruv; + + if (ruv_init_from_slapi_attr(sattr, &ruv) == 0) + { + ra->consumerRUV = object_new (ruv, (FNFree)ruv_destroy); + } + } + + ra->consumerRID = 0; + + /* DN and RDN of the replication agreement entry itself */ + ra->dn = slapi_sdn_dup(slapi_entry_get_sdn((Slapi_Entry *)e)); + ra->rdn = slapi_rdn_new_sdn(ra->dn); + + /* Compute long name */ + { + const char *agmtname = slapi_rdn_get_rdn(ra->rdn); + char hostname[128]; + char *dot; + + strncpy(hostname, ra->hostname ? ra->hostname : "(unknown)", sizeof(hostname)); + hostname[sizeof(hostname)-1] = '\0'; + dot = strchr(hostname, '.'); + if (dot) { + *dot = '\0'; + } + ra->long_name = slapi_ch_malloc(strlen(agmtname) + + strlen(hostname) + 25); + sprintf(ra->long_name, "agmt=\"%s\" (%s:%d)", agmtname, hostname, ra->port); + } + + /* Initialize status information */ + ra->last_update_start_time = 0UL; + ra->last_update_end_time = 0UL; + ra->num_changecounters = 0; + ra->last_update_status[0] = '\0'; + ra->update_in_progress = PR_FALSE; + ra->stop_in_progress = PR_FALSE; + ra->last_init_end_time = 0UL; + ra->last_init_start_time = 0UL; + ra->last_init_status[0] = '\0'; + + if (!agmt_is_valid(ra)) + { + goto loser; + } + + /* Now that the agreement is done, just check if changelog is configured */ + if (cl5GetState() != CL5_STATE_OPEN) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "WARNING: " + "Replication agreement added but there is no changelog configured. " + "No change will be replicated until a changelog is configured.\n"); + } + + /* + * Establish a callback for this agreement's entry, so we can + * adorn it with status information when read. + */ + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, slapi_sdn_get_ndn(ra->dn), + LDAP_SCOPE_BASE, "(objectclass=*)", get_agmt_status, ra); + + return ra; +loser: + agmt_delete((void **)&ra); + return NULL; +} + + + +Repl_Agmt * +agmt_new_from_pblock(Slapi_PBlock *pb) +{ + Slapi_Entry *e; + + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + return agmt_new_from_entry(e); +} + + +/* + This should never be called directly - only should be called + as a destructor. XXXggood this is not finished + */ +void +agmt_delete(void **rap) +{ + Repl_Agmt *ra; + PR_ASSERT(NULL != rap); + PR_ASSERT(NULL != *rap); + + ra = (Repl_Agmt *)*rap; + + /* do prot_delete first - we may be doing some processing using this + replication agreement, and prot_delete will make sure the + processing is complete - then it should be safe to clean up the + other fields below + */ + prot_delete(&ra->protocol); + + /* + * Remove the callback for this agreement's entry + */ + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, + slapi_sdn_get_ndn(ra->dn), + LDAP_SCOPE_BASE, "(objectclass=*)", + get_agmt_status); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&(ra->hostname)); + slapi_ch_free((void **)&(ra->binddn)); + + if (NULL != ra->creds) + { + /* XXX free berval */ + } + if (NULL != ra->replarea) + { + slapi_sdn_free(&ra->replarea); + } + + if (NULL != ra->consumerRUV) + { + object_release (ra->consumerRUV); + } + + csn_free (&ra->consumerSchemaCSN); + while ( --(ra->num_changecounters) >= 0 ) + { + slapi_ch_free((void **)&ra->changecounters[ra->num_changecounters]); + } + + schedule_destroy(ra->schedule); + slapi_ch_free((void **)&ra->long_name); + slapi_ch_free((void **)rap); +} + + +/* + * Allow replication for this replica to begin. Replication will + * occur at the next scheduled time. Returns 0 on success, -1 on + * failure. + */ +int +agmt_start(Repl_Agmt *ra) +{ + Repl_Protocol *prot = NULL; + + int protocol_state; + + /* To Allow Consumer Initialisation when adding an agreement: */ + if (ra->auto_initialize == STATE_PERFORMING_TOTAL_UPDATE) + { + protocol_state = STATE_PERFORMING_TOTAL_UPDATE; + } + else + { + protocol_state = STATE_PERFORMING_INCREMENTAL_UPDATE; + } + + /* First, create a new protocol object */ + if ((prot = prot_new(ra, protocol_state)) == NULL) { + return -1; + } + + /* Now it is safe to own the agreement lock */ + PR_Lock(ra->lock); + + /* Check that replication is not already started */ + if (ra->protocol != NULL) { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replication already started for agreement \"%s\"\n", agmt_get_long_name(ra)); + PR_Unlock(ra->lock); + prot_free(&prot); + return 0; + } + + ra->protocol = prot; + + /* Start the protocol thread */ + prot_start(ra->protocol); + + PR_Unlock(ra->lock); + return 0; +} + +/* +Cease replicating to this replica as soon as possible. +*/ +int +agmt_stop(Repl_Agmt *ra) +{ + int return_value = 0; + Repl_Protocol *rp = NULL; + + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + ra->stop_in_progress = PR_TRUE; + rp = ra->protocol; + PR_Unlock(ra->lock); + if (NULL != rp) /* we use this pointer outside the lock - dangerous? */ + { + prot_stop(rp); + } + PR_Lock(ra->lock); + ra->stop_in_progress = PR_FALSE; + /* we do not reuse the protocol object so free it */ + prot_free(&ra->protocol); + PR_Unlock(ra->lock); + return return_value; +} + +/* +Send any pending updates as soon as possible, ignoring any replication +schedules. +*/ +int +agmt_replicate_now(Repl_Agmt *ra) +{ + int return_value = 0; + + return return_value; +} + +/* + * Return a copy of the remote replica's hostname. + */ +char * +agmt_get_hostname(const Repl_Agmt *ra) +{ + char *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = slapi_ch_strdup(ra->hostname); + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return the port number of the remote replica's instance. + */ +int +agmt_get_port(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->port; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return the transport flags for this agreement. + */ +PRUint32 +agmt_get_transport_flags(const Repl_Agmt *ra) +{ + unsigned int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->transport_flags; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return a copy of the bind dn to be used with this + * agreement (may return NULL if no binddn is required, + * e.g. SSL client auth. + */ +char * +agmt_get_binddn(const Repl_Agmt *ra) +{ + char *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->binddn == NULL ? NULL : slapi_ch_strdup(ra->binddn); + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return a copy of the credentials. + */ +struct berval * +agmt_get_credentials(const Repl_Agmt *ra) +{ + struct berval *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + return_value->bv_val = (char *)slapi_ch_malloc(ra->creds->bv_len + 1); + return_value->bv_len = ra->creds->bv_len; + memcpy(return_value->bv_val, ra->creds->bv_val, ra->creds->bv_len); + return_value->bv_val[return_value->bv_len] = '\0'; /* just in case */ + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_get_bindmethod(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->bindmethod; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Return a copy of the dn at the top of the replicated area. + */ +Slapi_DN * +agmt_get_replarea(const Repl_Agmt *ra) +{ + Slapi_DN *return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = slapi_sdn_new(); + slapi_sdn_copy(ra->replarea, return_value); + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_is_fractional(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->frac_attrs != NULL; + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = 1; /* XXXggood finish this */ + PR_Unlock(ra->lock); + return return_value; +} + +int +agmt_get_auto_initialize(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->auto_initialize; + PR_Unlock(ra->lock); + return return_value; +} + +long +agmt_get_timeout(const Repl_Agmt *ra) +{ + long return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->timeout; + PR_Unlock(ra->lock); + return return_value; +} + +long +agmt_get_busywaittime(const Repl_Agmt *ra) +{ + long return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->busywaittime; + PR_Unlock(ra->lock); + return return_value; +} +long +agmt_get_pausetime(const Repl_Agmt *ra) +{ + long return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + return_value = ra->pausetime; + PR_Unlock(ra->lock); + return return_value; +} + +/* + * Warning - reference to the long name of the agreement is returned. + * The long name of an agreement is the DN of the agreement entry, + * followed by the host/port for the replica. + */ +const char * +agmt_get_long_name(const Repl_Agmt *ra) +{ + char *return_value = NULL; + + return_value = ra ? ra->long_name : ""; + return return_value; +} + +/* + * Warning - reference to dn is returned. However, since the dn of + * the replication agreement is its name, it won't change during the + * lifetime of the replication agreement object. + */ +const Slapi_DN * +agmt_get_dn_byref(const Repl_Agmt *ra) +{ + const Slapi_DN *return_value = NULL; + + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + return_value = ra->dn; + } + return return_value; +} + +/* Return 1 if name matches the replication Dn, 0 otherwise */ +int +agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name) +{ + int return_value = 0; + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + PR_Lock(ra->lock); + if (slapi_sdn_compare(name, ra->dn) == 0) + { + return_value = 1; + } + PR_Unlock(ra->lock); + } + return return_value; +} + +/* Return 1 if name matches the replication area, 0 otherwise */ +int +agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name) +{ + int return_value = 0; + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + PR_Lock(ra->lock); + if (slapi_sdn_compare(name, ra->replarea) == 0) + { + return_value = 1; + } + PR_Unlock(ra->lock); + } + return return_value; +} + + +int +agmt_schedule_in_window_now(const Repl_Agmt *ra) +{ + int return_value; + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (NULL != ra->schedule && schedule_in_window_now(ra->schedule)) + { + return_value = 1; + } + else + { + return_value = 0; + } + PR_Unlock(ra->lock); + return return_value; +} + + +/* + * Set or reset the credentials used to bind to the remote replica. + * + * Returns 0 if credentials set, or -1 if an error occurred. + */ +int +agmt_set_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr); + PR_Lock(ra->lock); + slapi_ch_free((void **)&ra->creds->bv_val); + ra->creds->bv_len = 0; + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + const struct berval *bv = slapi_value_get_berval(sval); + ra->creds->bv_val = slapi_ch_calloc(1, bv->bv_len + 1); + memcpy(ra->creds->bv_val, bv->bv_val, bv->bv_len); + ra->creds->bv_len = bv->bv_len; + } + } + /* If no credentials set, set to zero-length string */ + ra->creds->bv_val = NULL == ra->creds->bv_val ? slapi_ch_strdup("") : ra->creds->bv_val; + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + return return_value; +} + +/* + * Set or reset the DN used to bind to the remote replica. + * + * Returns 0 if DN set, or -1 if an error occurred. + */ +int +agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + slapi_entry_attr_find(e, type_nsds5ReplicaBindDN, &sattr); + PR_Lock(ra->lock); + slapi_ch_free((void **)&ra->binddn); + ra->binddn = NULL; + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + const char *val = slapi_value_get_string(sval); + ra->binddn = strdup(val); + } + } + /* If no BindDN set, set to zero-length string */ + if (ra->binddn == NULL) { + ra->binddn = strdup(""); + } + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + return return_value; +} + +/* + * Set or reset the bind method used to bind to the remote replica. + * + * Returns 0 if bind method set, or -1 if an error occurred. + */ +static int +agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) +{ + char *tmpstr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBindMethod); + + if (NULL == tmpstr || strcasecmp(tmpstr, "SIMPLE") == 0) + { + ra->bindmethod = BINDMETHOD_SIMPLE_AUTH; + } + else if (strcasecmp(tmpstr, "SSLCLIENTAUTH") == 0) + { + ra->bindmethod = BINDMETHOD_SSL_CLIENTAUTH; + } + else + { + ra->bindmethod = BINDMETHOD_SIMPLE_AUTH; + } + slapi_ch_free((void **)&tmpstr); + return return_value; +} + +int +agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + char *tmpstr = NULL; + int return_value = 0; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + return_value = agmt_set_bind_method_no_lock(ra, e); + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + return return_value; +} + +/* + * Set or reset the transport used to bind to the remote replica. + * + * Returns 0 if transport set, or -1 if an error occurred. + */ +static int +agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) +{ + char *tmpstr; + int rc = 0; + + tmpstr = slapi_entry_attr_get_charptr(e, type_nsds5TransportInfo); + if (NULL != tmpstr && strcasecmp(tmpstr, "SSL") == 0) + { + ra->transport_flags |= TRANSPORT_FLAG_SSL; + } else { + ra->transport_flags &= ~TRANSPORT_FLAG_SSL; + } + + slapi_ch_free((void **)&tmpstr); + return (rc); +} + +int +agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + int return_value = 0; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + return_value = agmt_set_transportinfo_no_lock(ra, e); + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + + return return_value; +} + + +/* + * Set or reset the replication schedule. Notify the protocol handler + * that a change has been made. + * + * Returns 0 if schedule was set or -1 if an error occurred. + */ +int +agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ) +{ + Slapi_Attr *sattr; + int return_value = 0; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + PR_Unlock(ra->lock); + + if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) != 0) + { + sattr = NULL; /* no schedule ==> delete any existing one */ + } + + /* make it so */ + return_value = schedule_set(ra->schedule, sattr); + + if ( 0 == return_value ) { + /* schedule set OK -- spread the news */ + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + + return return_value; +} + +/* + * Set or reset the timeout used to bind to the remote replica. + * + * Returns 0 if timeout set, or -1 if an error occurred. + */ +int +agmt_set_timeout_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = -1; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + + slapi_entry_attr_find(e, type_nsds5ReplicaTimeout, &sattr); + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + long tmpval = slapi_value_get_long(sval); + if (tmpval >= 0) { + ra->timeout = tmpval; + return_value = 0; /* success! */ + } + } + } + PR_Unlock(ra->lock); + if (return_value == 0) + { + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + return return_value; +} + +/* + * Set or reset the busywaittime + * + * Returns 0 if busywaittime set, or -1 if an error occurred. + */ +int +agmt_set_busywaittime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = -1; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + + slapi_entry_attr_find(e, type_nsds5ReplicaBusyWaitTime, &sattr); + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + long tmpval = slapi_value_get_long(sval); + if (tmpval >= 0) { + ra->busywaittime = tmpval; + return_value = 0; /* success! */ + } + } + } + PR_Unlock(ra->lock); + if (return_value == 0) + { + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + return return_value; +} + +/* + * Set or reset the pausetime + * + * Returns 0 if pausetime set, or -1 if an error occurred. + */ +int +agmt_set_pausetime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int return_value = -1; + + PR_ASSERT(NULL != ra); + PR_Lock(ra->lock); + if (ra->stop_in_progress) + { + PR_Unlock(ra->lock); + return return_value; + } + + slapi_entry_attr_find(e, type_nsds5ReplicaSessionPauseTime, &sattr); + if (NULL != sattr) + { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) + { + long tmpval = slapi_value_get_long(sval); + if (tmpval >= 0) { + ra->pausetime = tmpval; + return_value = 0; /* success! */ + } + } + } + PR_Unlock(ra->lock); + if (return_value == 0) + { + prot_notify_agmt_changed(ra->protocol, ra->long_name); + } + return return_value; +} + +/* XXXggood - also make this pass an arg that tells if there was + * an update to a priority attribute */ +void +agmt_notify_change(Repl_Agmt *agmt, Slapi_PBlock *pb) +{ + if (NULL != pb) + { + /* Is the entry within our replicated area? */ + char *target_dn; + Slapi_DN *target_sdn; + int change_is_relevant = 0; + + PR_ASSERT(NULL != agmt); + PR_Lock(agmt->lock); + if (agmt->stop_in_progress) + { + PR_Unlock(agmt->lock); + return; + } + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &target_dn); + target_sdn = slapi_sdn_new_dn_byref(target_dn); /* XXX see if you can avoid allocating this */ + + if (slapi_sdn_issuffix(target_sdn, agmt->replarea)) + { + /* + * Yep, it's in our replicated area. Is this a fractional + * replication agreement? + */ + if (NULL != agmt->frac_attrs) + { + /* + * Yep, it's fractional. See if the change should be + * tossed because it doesn't affect any of the replicated + * attributes. + */ + int optype; + int affects_fractional_attribute = 0; + + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype); + if (SLAPI_OPERATION_MODIFY == optype) + { + LDAPMod **mods; + int i, j; + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (i = 0; !affects_fractional_attribute && NULL != agmt->frac_attrs[i]; i++) + { + for (j = 0; !affects_fractional_attribute && NULL != mods[j]; j++) + { + if (slapi_attr_types_equivalent(agmt->frac_attrs[i], + mods[i]->mod_type)) + { + affects_fractional_attribute = 1; + } + } + } + } + else + { + /* + * Add, delete, and modrdn always cause some sort of + * operation replay, even if agreement is fractional. + */ + affects_fractional_attribute = 1; + } + if (affects_fractional_attribute) + { + change_is_relevant = 1; + } + } + else + { + /* Not a fractional agreement */ + change_is_relevant = 1; + } + } + PR_Unlock(agmt->lock); + slapi_sdn_free(&target_sdn); + if (change_is_relevant) + { + /* Notify the protocol that a change has occurred */ + prot_notify_update(agmt->protocol); + } + } +} + + + +int +agmt_is_50_mm_protocol(const Repl_Agmt *agmt) +{ + return 1; /* XXXggood could support > 1 protocol */ +} + + + +int +agmt_initialize_replica(const Repl_Agmt *agmt) +{ + PR_ASSERT(NULL != agmt); + PR_Lock(agmt->lock); + if (agmt->stop_in_progress) + { + PR_Unlock(agmt->lock); + return 0; + } + PR_Unlock(agmt->lock); + /* Call prot_initialize_replica only if the suffix is enabled (agmt->protocol != NULL) */ + if (NULL != agmt->protocol) { + prot_initialize_replica(agmt->protocol); + } + else { + /* agmt->protocol == NULL --> Suffix is disabled */ + return -1; + } + return 0; +} + +/* delete nsds5BeginReplicaRefresh attribute to indicate to the clients + that replica initialization have completed */ +void +agmt_replica_init_done (const Repl_Agmt *agmt) +{ + int rc; + Slapi_PBlock *pb = slapi_pblock_new (); + LDAPMod *mods [2]; + LDAPMod mod; + + mods[0] = &mod; + mods[1] = NULL; + mod.mod_op = LDAP_MOD_DELETE | LDAP_MOD_BVALUES; + mod.mod_type = (char*)type_nsds5ReplicaInitialize; + mod.mod_bvalues = NULL; + + slapi_modify_internal_set_pb(pb, slapi_sdn_get_dn (agmt->dn), mods, NULL/* controls */, + NULL/* uniqueid */, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0/* flags */); + slapi_modify_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_replica_init_done: " + "failed to remove (%s) attribute from (%s) entry; LDAP error - %d\n", + type_nsds5ReplicaInitialize, slapi_sdn_get_ndn (agmt->dn), rc); + } + + slapi_pblock_destroy (pb); +} + +/* Agreement object is acquired on behalf of the caller. + The caller is responsible for releasing the object + when it is no longer used */ + +Object* +agmt_get_consumer_ruv (Repl_Agmt *ra) +{ + Object *rt = NULL; + + PR_ASSERT(NULL != ra); + + PR_Lock(ra->lock); + if (ra->consumerRUV) + { + object_acquire (ra->consumerRUV); + rt = ra->consumerRUV; + } + + PR_Unlock(ra->lock); + + return rt; +} + +int +agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv) +{ + if (ra == NULL || ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmt_set_consumer_ruv: invalid argument" + " agmt - %p, ruv - %p\n", ra, ruv); + return -1; + } + + PR_Lock(ra->lock); + + if (ra->consumerRUV) + { + object_release (ra->consumerRUV); + } + + ra->consumerRUV = object_new (ruv_dup (ruv), (FNFree)ruv_destroy); + + PR_Unlock(ra->lock); + + return 0; +} + +void +agmt_update_consumer_ruv (Repl_Agmt *ra) +{ + int rc; + RUV *ruv; + Slapi_Mod smod; + Slapi_Mod smod_last_modified; + Slapi_PBlock *pb; + LDAPMod *mods[3]; + + PR_ASSERT (ra); + PR_Lock(ra->lock); + + if (ra->consumerRUV) + { + ruv = (RUV*) object_get_data (ra->consumerRUV); + PR_ASSERT (ruv); + + ruv_to_smod(ruv, &smod); + ruv_last_modified_to_smod(ruv, &smod_last_modified); + + /* it is ok to release the lock here because we are done with the agreement data. + we have to do it before issuing the modify operation because it causes + agmtlist_notify_all to be called which uses the same lock - hence the deadlock */ + PR_Unlock(ra->lock); + + pb = slapi_pblock_new (); + mods[0] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod); + mods[1] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_last_modified); + mods[2] = NULL; + + slapi_modify_internal_set_pb (pb, (char*)slapi_sdn_get_dn(ra->dn), mods, NULL, NULL, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modify_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: agmt_update_consumer_ruv: " + "failed to update consumer's RUV; LDAP error - %d\n", + ra->long_name, rc); + } + + slapi_mod_done (&smod); + slapi_mod_done (&smod_last_modified); + slapi_pblock_destroy (pb); + } + else + PR_Unlock(ra->lock); +} + +CSN* +agmt_get_consumer_schema_csn (Repl_Agmt *ra) +{ + CSN *rt; + + PR_ASSERT(NULL != ra); + + PR_Lock(ra->lock); + rt = ra->consumerSchemaCSN; + PR_Unlock(ra->lock); + + return rt; +} + +void +agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn) +{ + PR_ASSERT(NULL != ra); + + PR_Lock(ra->lock); + csn_free(&ra->consumerSchemaCSN); + ra->consumerSchemaCSN = csn; + PR_Unlock(ra->lock); +} + +void +agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_update_start_time = start_time; + ra->last_update_end_time = 0UL; + } +} + + +void +agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_update_end_time = end_time; + } +} + +void +agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_init_start_time = start_time; + ra->last_init_end_time = 0UL; + } +} + + +void +agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->last_init_end_time = end_time; + } +} + +void +agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *message) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + if (replrc == NSDS50_REPL_UPTODATE) + { + /* no session started, no status update */ + } + else if (ldaprc != LDAP_SUCCESS) + { + char *replmsg = NULL; + if ( replrc ) { + replmsg = protocol_response2string(replrc); + /* Do not mix the unknown replication error with the known ldap one */ + if ( strcasecmp(replmsg, "unknown error") == 0 ) { + replmsg = NULL; + } + } + if (ldaprc > 0) { + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d %s%sLDAP error: %s%s%s", + ldaprc, + message?message:"",message?"":" - ", + ldap_err2string(ldaprc), + replmsg ? " - " : "", replmsg ? replmsg : ""); + } else { /* ldaprc is < 0 */ + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d %s%sSystem error%s%s", + ldaprc,message?message:"",message?"":" - ", + replmsg ? " - " : "", replmsg ? replmsg : ""); + } + } + else if (replrc != 0) + { + if (replrc == NSDS50_REPL_REPLICA_READY) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", + ldaprc, "Replica acquired successfully"); + } + else if (replrc == NSDS50_REPL_REPLICA_BUSY) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d Can't acquire busy replica", replrc ); + } + else if (replrc == NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", + ldaprc, "Replication session successful"); + } + else if (replrc == NSDS50_REPL_DISABLED) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d Total update aborted: " + "Replication agreement for %s\n can not be updated while the replica is disabled.\n" + "(If the suffix is disabled you must enable it then restart the server for replication to take place).", + replrc, ra->long_name ? ra->long_name : "a replica"); + /* Log into the errors log, as "ra->long_name" is not accessible from the caller */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Total update aborted: Replication agreement for \"%s\" " + "can not be updated while the replica is disabled\n", ra->long_name ? ra->long_name : "a replica"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "(If the suffix is disabled you must enable it then restart the server for replication to take place).\n"); + } + else + { + PR_snprintf(ra->last_update_status, STATUS_LEN, + "%d Replication error acquiring replica: %s%s%s", + replrc, protocol_response2string(replrc), + message?" - ":"",message?message:""); + } + } + else if (message != NULL) + { + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d %s", ldaprc, message); + } + else { /* agmt_set_last_update_status(0,0,NULL) to reset agmt */ + PR_snprintf(ra->last_update_status, STATUS_LEN, "%d", ldaprc); + } + } +} + +void +agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *message) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + if (ldaprc != LDAP_SUCCESS) + { + char *replmsg = NULL; + if ( replrc ) { + replmsg = protocol_response2string(replrc); + /* Do not mix the unknown replication error with the known ldap one */ + if ( strcasecmp(replmsg, "unknown error") == 0 ) { + replmsg = NULL; + } + } + if (ldaprc > 0) { + PR_snprintf(ra->last_init_status, STATUS_LEN, + "%d %s%sLDAP error: %s%s%s", + ldaprc, + message?message:"",message?"":" - ", + ldap_err2string(ldaprc), + replmsg ? " - " : "", replmsg ? replmsg : ""); + } else { /* ldaprc is < 0 */ + PR_snprintf(ra->last_init_status, STATUS_LEN, + "%d %s%sSystem error%s%s", + ldaprc,message?message:"",message?"":" - ", + replmsg ? " - " : "", replmsg ? replmsg : ""); + } + } + else if (replrc != 0) + { + if (replrc == NSDS50_REPL_REPLICA_READY) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", + ldaprc, "Replica acquired successfully"); + } + else if (replrc == NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", + ldaprc, "Replication session successful"); + } + else if (replrc == NSDS50_REPL_DISABLED) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d Total update aborted: " + "Replication agreement for %s\n can not be updated while the replica is disabled.\n" + "(If the suffix is disabled you must enable it then restart the server for replication to take place).", + replrc, ra->long_name ? ra->long_name : "a replica"); + /* Log into the errors log, as "ra->long_name" is not accessible from the caller */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Total update aborted: Replication agreement for \"%s\" " + "can not be updated while the replica is disabled\n", ra->long_name ? ra->long_name : "a replica"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "(If the suffix is disabled you must enable it then restart the server for replication to take place).\n"); + } + else + { + PR_snprintf(ra->last_init_status, STATUS_LEN, + "%d Replication error acquiring replica: %s%s%s", + replrc, protocol_response2string(replrc), + message?" - ":"",message?message:""); + } + } + else if (message != NULL) + { + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d %s", ldaprc, message); + } + else { /* agmt_set_last_init_status(0,0,NULL) to reset agmt */ + PR_snprintf(ra->last_init_status, STATUS_LEN, "%d", ldaprc); + } + } +} + + +void +agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + ra->update_in_progress = in_progress; + } +} + +void +agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped) +{ + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + int i; + + for ( i = 0; i < ra->num_changecounters; i++ ) + { + if ( ra->changecounters[i]->rid == rid ) + break; + } + + if ( i < ra->num_changecounters ) + { + if ( skipped ) + ra->changecounters[i]->num_skipped ++; + else + ra->changecounters[i]->num_replayed ++; + } + else + { + ra->num_changecounters ++; + ra->changecounters[i] = (struct changecounter*) slapi_ch_calloc(1, sizeof(struct changecounter)); + ra->changecounters[i]->rid = rid; + if ( skipped ) + ra->changecounters[i]->num_skipped = 1; + else + ra->changecounters[i]->num_replayed = 1; + } + } +} + +void +agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize) +{ + char tmp_buf[32]; /* 5 digit RID, 10 digit each replayed and skipped */ + int i; + int buflen = 0; + + *buf = '\0'; + if (NULL != ra) + { + for ( i = 0; i < ra->num_changecounters; i++ ) + { + PR_snprintf (tmp_buf, sizeof(tmp_buf), "%u:%u/%u ", + ra->changecounters[i]->rid, + ra->changecounters[i]->num_replayed, + ra->changecounters[i]->num_skipped); + PR_snprintf (buf+buflen, bufsize-buflen, "%s", tmp_buf); + buflen += strlen (tmp_buf); + } + } +} + +static int +get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + char *time_tmp = NULL; + char changecount_string[BUFSIZ]; + Repl_Agmt *ra = (Repl_Agmt *)arg; + + PR_ASSERT(NULL != ra); + if (NULL != ra) + { + PRBool reapActive = PR_FALSE; + Slapi_DN *replarea_sdn = NULL; + Object *repl_obj = NULL; + + replarea_sdn = agmt_get_replarea(ra); + repl_obj = replica_get_replica_from_dn(replarea_sdn); + slapi_sdn_free(&replarea_sdn); + if (repl_obj) { + Replica *replica = (Replica*)object_get_data (repl_obj); + reapActive = replica_get_tombstone_reap_active(replica); + object_release(repl_obj); + } + slapi_entry_attr_set_int(e, "nsds5replicaReapActive", (int)reapActive); + + /* these values persist in the dse.ldif file, so we delete them + here to avoid multi valued attributes */ + slapi_entry_attr_delete(e, "nsds5replicaLastUpdateStart"); + slapi_entry_attr_delete(e, "nsds5replicaLastUpdateEnd"); + slapi_entry_attr_delete(e, "nsds5replicaChangesSentSinceStartup"); + slapi_entry_attr_delete(e, "nsds5replicaLastUpdateStatus"); + slapi_entry_attr_delete(e, "nsds5replicaUpdateInProgress"); + slapi_entry_attr_delete(e, "nsds5replicaLastInitStart"); + slapi_entry_attr_delete(e, "nsds5replicaLastInitStatus"); + slapi_entry_attr_delete(e, "nsds5replicaLastInitEnd"); + + /* now, add the real values (singly) */ + if (ra->last_update_start_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateStart", "0"); + } + else + { + time_tmp = format_genTime(ra->last_update_start_time); + slapi_entry_add_string(e, "nsds5replicaLastUpdateStart", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + if (ra->last_update_end_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateEnd", "0"); + } + else + { + time_tmp = format_genTime(ra->last_update_end_time); + slapi_entry_add_string(e, "nsds5replicaLastUpdateEnd", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + agmt_get_changecount_string (ra, changecount_string, sizeof (changecount_string) ); + slapi_entry_add_string(e, "nsds5replicaChangesSentSinceStartup", changecount_string); + if (ra->last_update_status[0] == '\0') + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateStatus", "0 No replication sessions started since server startup"); + } + else + { + slapi_entry_add_string(e, "nsds5replicaLastUpdateStatus", ra->last_update_status); + } + slapi_entry_add_string(e, "nsds5replicaUpdateInProgress", ra->update_in_progress ? "TRUE" : "FALSE"); + if (ra->last_init_start_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastInitStart", "0"); + } + else + { + time_tmp = format_genTime(ra->last_init_start_time); + slapi_entry_add_string(e, "nsds5replicaLastInitStart", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + if (ra->last_init_end_time == 0) + { + slapi_entry_add_string(e, "nsds5replicaLastInitEnd", "0"); + } + else + { + time_tmp = format_genTime(ra->last_init_end_time); + slapi_entry_add_string(e, "nsds5replicaLastInitEnd", time_tmp); + slapi_ch_free((void **)&time_tmp); + } + if (ra->last_init_status[0] != '\0') + { + slapi_entry_add_string(e, "nsds5replicaLastInitStatus", ra->last_init_status); + } + } + return SLAPI_DSE_CALLBACK_OK; +} + +static void +update_window_state_change_callback (void *arg, PRBool opened) +{ + Repl_Agmt *agmt = (Repl_Agmt*)arg; + + PR_ASSERT (agmt); + + if (opened) + { + prot_notify_window_opened (agmt->protocol); + } + else + { + prot_notify_window_closed (agmt->protocol); + } +} + +ReplicaId +agmt_get_consumer_rid ( Repl_Agmt *agmt, void *conn ) +{ + if ( agmt->consumerRID <= 0 ) { + + char mapping_tree_node[512]; + struct berval **bvals = NULL; + + PR_snprintf ( mapping_tree_node, + sizeof (mapping_tree_node), + "cn=replica,cn=\"%s\",cn=mapping tree,cn=config", + slapi_sdn_get_dn (agmt->replarea) ); + conn_read_entry_attribute ( conn, mapping_tree_node, "nsDS5ReplicaID", &bvals ); + if ( NULL != bvals && NULL != bvals[0] ) { + char *ridstr = slapi_ch_malloc( bvals[0]->bv_len + 1 ); + memcpy ( ridstr, bvals[0]->bv_val, bvals[0]->bv_len ); + ridstr[bvals[0]->bv_len] = '\0'; + agmt->consumerRID = atoi (ridstr); + slapi_ch_free ( (void**) &ridstr ); + ber_bvecfree ( bvals ); + } + } + + return agmt->consumerRID; +} diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c new file mode 100644 index 00000000..5c7213a6 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_agmtlist.c @@ -0,0 +1,618 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_agmtlist.c */ +/* + + Replication agreements are held in object set (objset.c). + +*/ + +#include "repl5.h" + +#define AGMT_CONFIG_BASE "cn=mapping tree, cn=config" +#define CONFIG_FILTER "(objectclass=nsds5replicationagreement)" + +PRCallOnceType once = {0}; +static Objset *agmt_set = NULL; /* The set of replication agreements */ + +typedef struct agmt_wrapper { + Repl_Agmt *agmt; + void *handle; +} agmt_wrapper; + + + +/* + * Find the replication agreement whose entry DN matches the given DN. + * Object is returned referenced, so be sure to release it when + * finished. + */ +Repl_Agmt * +agmtlist_get_by_agmt_name(const Slapi_DN *agmt_name) +{ + Repl_Agmt *ra; + Object *ro; + + for (ro = objset_first_obj(agmt_set); NULL != ro; + ro = objset_next_obj(agmt_set, ro)) + { + ra = (Repl_Agmt *)object_get_data(ro); + if (agmt_matches_name(ra, agmt_name)) + { + break; + } + } + return ra; +} + + +static int +agmt_ptr_cmp(Object *ro, const void *arg) +{ + Repl_Agmt *ra; + Repl_Agmt *provided_ra = (Repl_Agmt *)arg; + + ra = object_get_data(ro); + + if (ra == provided_ra) + return 0; + else + return 1; +} + + + +static int +agmt_dn_cmp(Object *ro, const void *arg) +{ + Repl_Agmt *ra; + Slapi_DN *sdn = (Slapi_DN *)arg; + + ra = object_get_data(ro); + return(slapi_sdn_compare(sdn, agmt_get_dn_byref(ra))); +} + +void +agmtlist_release_agmt(Repl_Agmt *ra) +{ + Object *ro; + + PR_ASSERT(NULL != agmt_set); + PR_ASSERT(NULL != ra); + + ro = objset_find(agmt_set, agmt_ptr_cmp, (const void *)ra); + if (NULL != ro) + { + /* + * Release twice - once for the reference we got when finding + * it, and once for the reference we got when we called + * agmtlist_get_*(). + */ + object_release(ro); + object_release(ro); + } +} + + +/* + * Note: when we add the new object, we have a reference to it. We hold + * on to this reference until the agreement is deleted (or until the + * server is shut down). + */ +static int +add_new_agreement(Slapi_Entry *e) +{ + int rc = 0; + Repl_Agmt *ra = agmt_new_from_entry(e); + Slapi_DN *replarea_sdn = NULL; + Replica *replica = NULL; + Object *repl_obj = NULL; + Object *ro = NULL; + + if (ra == NULL) return 0; + + ro = object_new((void *)ra, agmt_delete); + objset_add_obj(agmt_set, ro); + object_release(ro); /* Object now owned by objset */ + + /* get the replica for this agreement */ + replarea_sdn = agmt_get_replarea(ra); + repl_obj = replica_get_replica_from_dn(replarea_sdn); + slapi_sdn_free(&replarea_sdn); + if (repl_obj) { + replica = (Replica*)object_get_data (repl_obj); + } + + rc = replica_start_agreement(replica, ra); + + if (repl_obj) object_release(repl_obj); + + return rc; +} + +static int +agmtlist_add_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, + int *returncode, char *returntext, void *arg) +{ + int rc; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmt_add: begin\n"); + + rc = add_new_agreement(e); + if (0 != rc) { + char *dn; + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_add_callback: " + "Can't start agreement \"%s\"\n", dn); + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; + } + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +static int +agmtlist_modify_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e, + int *returncode, char *returntext, void *arg) +{ + int i; + char *dn; + Slapi_DN *sdn = NULL; + int start_initialize = 0, stop_initialize = 0, cancel_initialize = 0; + int update_the_schedule = 0; /* do we need to update the repl sched? */ + Repl_Agmt *agmt = NULL; + LDAPMod **mods; + char buff [BUFSIZ]; + char *errortext = returntext ? returntext : buff; + int rc = SLAPI_DSE_CALLBACK_OK; + Slapi_Operation *op; + void *identity; + + *returncode = LDAP_SUCCESS; + + /* just let internal operations originated from replication plugin to go through */ + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + + if (operation_is_flag_set(op, OP_FLAG_INTERNAL) && + (identity == repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION))) + { + goto done; + } + + slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + sdn= slapi_sdn_new_dn_byref(dn); + agmt = agmtlist_get_by_agmt_name(sdn); + if (NULL == agmt) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_modify_callback: received " + "a modification for unknown replication agreement \"%s\"\n", dn); + goto done; + } + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (i = 0; NULL != mods && NULL != mods[i]; i++) + { + if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaInitialize)) + { + /* we don't allow delete attribute operations unless it was issued by + the replication plugin - handled above */ + if (mods[i]->mod_op & LDAP_MOD_DELETE) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "deletion of %s attribute is not allowed\n", type_nsds5ReplicaInitialize); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + else + { + char *val; + + if (mods[i]->mod_bvalues && mods[i]->mod_bvalues[0]) + val = slapi_berval_get_string_copy (mods[i]->mod_bvalues[0]); + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "no value provided for %s attribute\n", type_nsds5ReplicaInitialize); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + + /* Start replica initialization */ + if (val == NULL) + { + sprintf (errortext, "No value supplied for attr (%s)", mods[i]->mod_type); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: %s\n", + errortext); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + + if (strcasecmp (val, "start") == 0) + { + start_initialize = 1; + } + else if (strcasecmp (val, "stop") == 0) + { + stop_initialize = 1; + } + else if (strcasecmp (val, "cancel") == 0) + { + cancel_initialize = 1; + } + else + { + sprintf (errortext, "Invalid value (%s) value supplied for attr (%s)", + val, mods[i]->mod_type); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: %s\n", + errortext); + } + + slapi_ch_free ((void**)&val); + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaUpdateSchedule)) + { + /* + * Request to update the replication schedule. Set a flag so + * we know to update the schedule later. + */ + update_the_schedule = 1; + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaCredentials)) + { + /* New replica credentials */ + if (agmt_set_credentials_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update credentials for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaTimeout)) + { + /* New replica timeout */ + if (agmt_set_timeout_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update timeout for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBusyWaitTime)) + { + /* New replica busywaittime */ + if (agmt_set_busywaittime_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update busy wait time for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaSessionPauseTime)) + { + /* New replica pausetime */ + if (agmt_set_pausetime_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update session pause time for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBindDN)) + { + /* New replica Bind DN */ + if (agmt_set_binddn_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update bind DN for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5TransportInfo)) + { + /* New Transport info */ + if (agmt_set_transportinfo_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update transport info for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBindMethod)) + { + /* New replica bind method */ + if (agmt_set_bind_method_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update bind method for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + else if (slapi_attr_types_equivalent(mods[i]->mod_type, + "nsds5debugreplicatimeout")) + { + char *val = slapi_entry_attr_get_charptr(e, "nsds5debugreplicatimeout"); + repl5_set_debug_timeout(val); + slapi_ch_free_string(&val); + } + else if (strcasecmp (mods[i]->mod_type, "modifytimestamp") == 0 || + strcasecmp (mods[i]->mod_type, "modifiersname") == 0 || + strcasecmp (mods[i]->mod_type, "description") == 0) + { + /* ignore modifier's name and timestamp attributes and the description. */ + continue; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "modification of %s attribute is not allowed\n", mods[i]->mod_type); + *returncode = LDAP_UNWILLING_TO_PERFORM; + rc = SLAPI_DSE_CALLBACK_ERROR; + break; + } + } + + if (stop_initialize) + { + agmt_stop (agmt); + } + else if (start_initialize) + { + if (agmt_initialize_replica(agmt) != 0) { + /* The suffix is disabled */ + agmt_set_last_init_status(agmt, 0, NSDS50_REPL_DISABLED, NULL); + } + } + else if (cancel_initialize) + { + agmt_replica_init_done(agmt); + } + + if (update_the_schedule) + { + if (agmt_set_schedule_from_entry(agmt, e) != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: " + "failed to update replication schedule for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } + +done: + if (NULL != agmt) + { + agmtlist_release_agmt(agmt); + } + + if (sdn) + slapi_sdn_free(&sdn); + return rc; +} + +static int +agmtlist_delete_callback(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, + int *returncode, char *returntext, void *arg) +{ + Repl_Agmt *ra; + Object *ro; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_delete: begin\n"); + ro = objset_find(agmt_set, agmt_dn_cmp, (const void *)slapi_entry_get_sdn_const(e)); + ra = (NULL == ro) ? NULL : (Repl_Agmt *)object_get_data(ro); + if (NULL == ra) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmtlist_delete: " + "Tried to delete replication agreement \"%s\", but no such " + "agreement was configured.\n", slapi_sdn_get_dn(slapi_entry_get_sdn_const(e))); + } + else + { + agmt_stop(ra); + object_release(ro); /* Release ref acquired in objset_find */ + objset_remove_obj(agmt_set, ro); /* Releases a reference (should be final reference */ + } + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +static int +agmtlist_rename_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e, + int *returncode, char *returntext, void *arg) +{ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "agmt_rename: begin\n"); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + + +static int +handle_agmt_search(Slapi_Entry *e, void *callback_data) +{ + int *agmtcount = (int *)callback_data; + int rc; + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Found replication agreement named \"%s\".\n", + slapi_sdn_get_dn(slapi_entry_get_sdn(e))); + rc = add_new_agreement(e); + if (0 == rc) + { + (*agmtcount)++; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "The replication " + "agreement named \"%s\" could not be correctly parsed. No " + "replication will occur with this replica.\n", + slapi_sdn_get_dn(slapi_entry_get_sdn(e))); + } + + return rc; +} + + +static void +agmtlist_objset_destructor(void **o) +{ + /* XXXggood Nothing to do, I think. */ +} + + +int +agmtlist_config_init() +{ + Slapi_PBlock *pb; + int agmtcount = 0; + + agmt_set = objset_new(agmtlist_objset_destructor); + + /* Register callbacks so we're informed about updates */ + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_add_callback, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_modify_callback, NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_delete_callback, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, AGMT_CONFIG_BASE, + LDAP_SCOPE_SUBTREE, CONFIG_FILTER, agmtlist_rename_callback, NULL); + + /* Search the DIT and find all the replication agreements */ + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, AGMT_CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, NULL /* attrs */, 0 /* attrsonly */, + NULL, /* controls */ NULL /* uniqueid */, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* actions */); + slapi_search_internal_callback_pb(pb, + (void *)&agmtcount /* callback data */, + NULL /* result_callback */, + handle_agmt_search /* search entry cb */, + NULL /* referral callback */); + slapi_pblock_destroy(pb); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_config_init: found %d replication agreements in DIT\n", agmtcount); + + return 0; +} + + + +void +agmtlist_shutdown() +{ + Repl_Agmt *ra; + Object *ro; + Object *next_ro; + + ro = objset_first_obj(agmt_set); + while (NULL != ro) + { + next_ro = objset_next_obj(agmt_set, ro); + ra = (Repl_Agmt *)object_get_data(ro); + agmt_stop(ra); + agmt_update_consumer_ruv (ra); + objset_remove_obj(agmt_set, ro); + ro = next_ro; + } + objset_delete(&agmt_set); + agmt_set = NULL; +} + + + +/* + * Notify each replication agreement about an update. + */ +void +agmtlist_notify_all(Slapi_PBlock *pb) +{ + Repl_Agmt *ra; + Object *ro; + + if (NULL != agmt_set) + { + ro = objset_first_obj(agmt_set); + while (NULL != ro) + { + ra = (Repl_Agmt *)object_get_data(ro); + agmt_notify_change(ra, pb); + ro = objset_next_obj(agmt_set, ro); + } + } +} + +Object* agmtlist_get_first_agreement_for_replica (Replica *r) +{ + return agmtlist_get_next_agreement_for_replica (r, NULL) ; +} + +Object* agmtlist_get_next_agreement_for_replica (Replica *r, Object *prev) +{ + const Slapi_DN *replica_root; + Slapi_DN *agmt_root; + Object *obj; + Repl_Agmt *agmt; + + if (r == NULL) + { + /* ONREPL - log error */ + return NULL; + } + + replica_root = replica_get_root(r); + + if (prev) + obj = objset_next_obj(agmt_set, prev); + else + obj = objset_first_obj(agmt_set); + + while (obj) + { + agmt = (Repl_Agmt*)object_get_data (obj); + PR_ASSERT (agmt); + + agmt_root = agmt_get_replarea(agmt); + PR_ASSERT (agmt_root); + + if (slapi_sdn_compare (replica_root, agmt_root) == 0) + { + slapi_sdn_free (&agmt_root); + return obj; + } + + slapi_sdn_free (&agmt_root); + obj = objset_next_obj(agmt_set, obj); + } + + return NULL; +} diff --git a/ldap/servers/plugins/replication/repl5_backoff.c b/ldap/servers/plugins/replication/repl5_backoff.c new file mode 100644 index 00000000..d0a90878 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_backoff.c @@ -0,0 +1,232 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_backoff.c */ +/* + + The backoff object implements a backoff timer. The timer can operate + with a fixed interval, an expontially increasing interval, or a + random interval. + + The caller creates a new backoff timer, specifying the backoff behavior + desired (fixed, exponential, or random), the initial backoff value, + and the maximum backoff interval. This does not start the timer - the + backoff_reset() function must be used to actually start the timer. + + The backoff_reset() function takes an optional function that + will be called when the backoff time has expired, and a void * + that can be used to pass arguments into the callback function. + + When the time expires, the callback function will be called. If no + callback function has been provided, the timer simply expires. + A timer does not recompute the next interval and begin timing until + the backoff_step() function is called. Therefore, callers that + do not install a callback function may use the timer by polling. + When a callback function is provided, the timer is typically reset + inside the callback function. + +*/ + +#include "repl5.h" + + +typedef struct backoff_timer { + int type; + int running; + slapi_eq_fn_t callback; + void *callback_arg; + time_t initial_interval; + time_t next_interval; + time_t max_interval; + time_t last_fire_time; + Slapi_Eq_Context pending_event; + PRLock *lock; + +} backoff_timer; + +/* Forward declarations */ +static PRIntervalTime random_interval_in_range(time_t lower_bound, time_t upper_bound); + + +/* + Create a new backoff timer. The timer is initialized, but is not + started. + */ +Backoff_Timer * +backoff_new(int timer_type, int initial_interval, int max_interval) +{ + Backoff_Timer *bt; + + bt = (Backoff_Timer *)slapi_ch_calloc(1, sizeof(struct backoff_timer)); + bt->type = timer_type; + bt->initial_interval = initial_interval; + bt->next_interval = bt->initial_interval; + bt->max_interval = max_interval; + bt->running = 0; + if ((bt->lock = PR_NewLock()) == NULL) + { + slapi_ch_free((void **)&bt); + } + return bt; +} + + +/* + * Reset and start the timer. Returns the time (as a time_t) when the + * time will next expire. + */ +time_t +backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data) +{ + time_t return_value = 0UL; + + PR_ASSERT(NULL != bt); + PR_ASSERT(NULL != callback); + + PR_Lock(bt->lock); + bt->running = 1; + bt->callback = callback; + bt->callback_arg = callback_data; + /* Cancel any pending events in the event queue */ + if (NULL != bt->pending_event) + { + slapi_eq_cancel(bt->pending_event); + bt->pending_event = NULL; + } + /* Compute the first fire time */ + if (BACKOFF_RANDOM == bt->type) + { + bt->next_interval = random_interval_in_range(bt->initial_interval, + bt->max_interval); + } + else + { + bt->next_interval = bt->initial_interval; + } + /* Schedule the callback */ + time(&bt->last_fire_time); + return_value = bt->last_fire_time + bt->next_interval; + bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg, + return_value); + PR_Unlock(bt->lock); + return return_value; +} + + +/* + Step the timer - compute the new backoff interval and start + counting. Note that the next expiration time is based on the + last timer expiration time, *not* the current time. + + Returns the time (as a time_t) when the timer will next expire. + */ +time_t +backoff_step(Backoff_Timer *bt) +{ + time_t return_value = 0UL; + + PR_ASSERT(NULL != bt); + + /* If the timer has never been reset, then return 0 */ + PR_Lock(bt->lock); + if (bt->running) + { + time_t previous_interval = bt->next_interval; + switch (bt->type) { + case BACKOFF_FIXED: + /* Interval stays the same */ + break; + case BACKOFF_EXPONENTIAL: + /* Interval doubles, up to a maximum */ + if (bt->next_interval < bt->max_interval) + { + bt->next_interval *= 2; + if (bt->next_interval > bt->max_interval) + { + bt->next_interval = bt->max_interval; + } + } + break; + case BACKOFF_RANDOM: + /* Compute the new random interval time */ + bt->next_interval = random_interval_in_range(bt->initial_interval, + bt->max_interval); + break; + } + /* Schedule the callback, if any */ + bt->last_fire_time += previous_interval; + return_value = bt->last_fire_time + bt->next_interval; + bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg, + return_value); + } + PR_Unlock(bt->lock); + return return_value; +} + + +/* + * Return 1 if the backoff timer has expired, 0 otherwise. + */ +int +backoff_expired(Backoff_Timer *bt, int margin) +{ + int return_value = 0; + + PR_ASSERT(NULL != bt); + PR_Lock(bt->lock); + return_value = (current_time() >= (bt->last_fire_time + bt->next_interval + margin)); + PR_Unlock(bt->lock); + return return_value; +} + + +/* + Destroy and deallocate a timer object + */ +void +backoff_delete(Backoff_Timer **btp) +{ + Backoff_Timer *bt; + + PR_ASSERT(NULL != btp && NULL != *btp); + bt = *btp; + PR_Lock(bt->lock); + /* Cancel any pending events in the event queue */ + if (NULL != bt->pending_event) + { + slapi_eq_cancel(bt->pending_event); + } + PR_Unlock(bt->lock); + PR_DestroyLock(bt->lock); + slapi_ch_free((void **)btp); +} + + +/* + * Return the next fire time for the timer. + */ +time_t +backoff_get_next_fire_time(Backoff_Timer *bt) +{ + time_t return_value; + + PR_ASSERT(NULL != bt); + PR_Lock(bt->lock); + return_value = bt->last_fire_time + bt->next_interval; + PR_Unlock(bt->lock); + return return_value; +} + +static PRIntervalTime +random_interval_in_range(time_t lower_bound, time_t upper_bound) +{ + /* + * slapi_rand() provides some entropy from two or three system timer + * calls (depending on the platform) down in NSS. If more entropy is + * required, slapi_rand_r(unsigned int *seed) can be called instead. + */ + return(lower_bound + (slapi_rand() % (upper_bound - lower_bound))); +} + diff --git a/ldap/servers/plugins/replication/repl5_connection.c b/ldap/servers/plugins/replication/repl5_connection.c new file mode 100644 index 00000000..a50c163a --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_connection.c @@ -0,0 +1,1493 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_connection.c */ +/* + + The connection object manages a connection to a single replication + consumer. + +XXXggood what to do on timeout? If we close connection, then we won't leave a +replica locked. Seems like right thing to do. +*/ + +#include "repl5.h" +#include "ldappr.h" + +typedef struct repl_connection +{ + char *hostname; + int port; + char *binddn; + int bindmethod; + int state; + int last_operation; + int last_ldap_error; + const char *status; + char *last_ldap_errmsg; + PRUint32 transport_flags; + LDAP *ld; + int supports_ldapv3; /* 1 if does, 0 if doesn't, -1 if not determined */ + int supports_ds50_repl; /* 1 if does, 0 if doesn't, -1 if not determined */ + int supports_ds40_repl; /* 1 if does, 0 if doesn't, -1 if not determined */ + int linger_time; /* time in seconds to leave an idle connection open */ + PRBool linger_active; + Slapi_Eq_Context *linger_event; + PRBool delete_after_linger; + int refcnt; + const Repl_Agmt *agmt; + PRLock *lock; + struct timeval timeout; + int flag_agmt_changed; + char *plain; +} repl_connection; + +/* #define DEFAULT_LINGER_TIME (5 * 60) */ /* 5 minutes */ +#define DEFAULT_LINGER_TIME (60) + +/* Controls we add on every outbound operation */ + +static LDAPControl manageDSAITControl = {LDAP_CONTROL_MANAGEDSAIT, {0, ""}, '\0'}; +static int attribute_string_value_present(LDAP *ld, LDAPMessage *entry, + const char *type, const char *value); +static int bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password); +static int do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password); + +static int s_debug_timeout = 0; +static int s_debug_level = 0; +static Slapi_Eq_Context repl5_start_debug_timeout(int *setlevel); +static void repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel); +static void repl5_debug_timeout_callback(time_t when, void *arg); +#ifndef DSE_RETURNTEXT_SIZE +#define SLAPI_DSE_RETURNTEXT_SIZE 512 +#endif + +#define STATE_CONNECTED 600 +#define STATE_DISCONNECTED 601 + +#define STATUS_DISCONNECTED "disconnected" +#define STATUS_CONNECTED "connected" +#define STATUS_PROCESSING_ADD "processing add operation" +#define STATUS_PROCESSING_DELETE "processing delete operation" +#define STATUS_PROCESSING_MODIFY "processing modify operation" +#define STATUS_PROCESSING_RENAME "processing rename operation" +#define STATUS_PROCESSING_EXTENDED_OPERATION "processing extended operation" +#define STATUS_LINGERING "lingering" +#define STATUS_SHUTTING_DOWN "shutting down" +#define STATUS_BINDING "connecting and binding" +#define STATUS_SEARCHING "processing search operation" + +#define CONN_NO_OPERATION 0 +#define CONN_ADD 1 +#define CONN_DELETE 2 +#define CONN_MODIFY 3 +#define CONN_RENAME 4 +#define CONN_EXTENDED_OPERATION 5 +#define CONN_BIND 6 +#define CONN_INIT 7 + +/* These are errors returned from ldap operations which should cause us to disconnect and + retry the connection later */ +#define IS_DISCONNECT_ERROR(rc) (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR || rc == LDAP_INVALID_CREDENTIALS || rc == LDAP_INAPPROPRIATE_AUTH || rc == LDAP_LOCAL_ERROR) + +/* Forward declarations */ +static void close_connection_internal(Repl_Connection *conn); + +/* + * Create a new conenction object. Returns a pointer to the object, or + * NULL if an error occurs. + */ +Repl_Connection * +conn_new(Repl_Agmt *agmt) +{ + Repl_Connection *rpc; + + rpc = (Repl_Connection *)slapi_ch_malloc(sizeof(repl_connection)); + if ((rpc->lock = PR_NewLock()) == NULL) + { + goto loser; + } + rpc->hostname = agmt_get_hostname(agmt); + rpc->port = agmt_get_port(agmt); + rpc->binddn = agmt_get_binddn(agmt); + rpc->bindmethod = agmt_get_bindmethod(agmt); + rpc->transport_flags = agmt_get_transport_flags(agmt); + rpc->ld = NULL; + rpc->state = STATE_DISCONNECTED; + rpc->last_operation = CONN_NO_OPERATION; + rpc->last_ldap_error = LDAP_SUCCESS; + rpc->last_ldap_errmsg = NULL; + rpc->supports_ldapv3 = -1; + rpc->supports_ds40_repl = -1; + rpc->supports_ds50_repl = -1; + rpc->linger_active = PR_FALSE; + rpc->delete_after_linger = PR_FALSE; + rpc->linger_event = NULL; + rpc->linger_time = DEFAULT_LINGER_TIME; + rpc->status = STATUS_DISCONNECTED; + rpc->agmt = agmt; + rpc->refcnt = 1; + rpc->timeout.tv_sec = agmt_get_timeout(agmt); + rpc->timeout.tv_usec = 0; + rpc->flag_agmt_changed = 0; + rpc->plain = NULL; + return rpc; +loser: + conn_delete(rpc); + return NULL; +} + + +/* + * Return PR_TRUE if the connection is in the connected state + */ +static PRBool +conn_connected(Repl_Connection *conn) +{ + PRBool return_value; + PR_Lock(conn->lock); + return_value = STATE_CONNECTED == conn->state; + PR_Unlock(conn->lock); + return return_value; +} + + +/* + * Destroy a connection object. + */ +static void +conn_delete_internal(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + close_connection_internal(conn); + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&conn->hostname); + slapi_ch_free((void **)&conn->binddn); + slapi_ch_free((void **)&conn->plain); +} + +/* + * Destroy a connection. It is an error to use the connection object + * after conn_delete() has been called. + */ +void +conn_delete(Repl_Connection *conn) +{ + PRBool destroy_it = PR_FALSE; + + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + if (conn->linger_active) + { + if (slapi_eq_cancel(conn->linger_event) == 1) + { + /* Event was found and cancelled. Destroy the connection object. */ + PR_Unlock(conn->lock); + destroy_it = PR_TRUE; + } + else + { + /* + * The event wasn't found, but we think it's still active. + * That means an event is in the process of being fired + * off, so arrange for the event to destroy the object . + */ + conn->delete_after_linger = PR_TRUE; + PR_Unlock(conn->lock); + } + } + if (destroy_it) + { + conn_delete_internal(conn); + } +} + + +/* + * Return the last operation type processed by the connection + * object, and the LDAP error encountered. + */ +void +conn_get_error(Repl_Connection *conn, int *operation, int *error) +{ + PR_Lock(conn->lock); + *operation = conn->last_operation; + *error = conn->last_ldap_error; + PR_Unlock(conn->lock); +} + + +/* + * Common code to send an LDAPv3 operation and collect the result. + * Return values: + * CONN_OPERATION_SUCCESS - the operation succeeded + * CONN_OPERATION_FAILED - the operation was sent to the consumer + * and failed. Use conn_get_error() to determine the LDAP error + * code. + * CONN_NOT_CONNECTED - no connection is active. The caller should + * use conn_connect() to connect to the replica and bind, then should + * reacquire the replica (if needed). + * CONN_BUSY - the server is busy with previous requests, must wait for a while + * before retrying + */ +static ConnResult +perform_operation(Repl_Connection *conn, int optype, const char *dn, + LDAPMod **attrs, const char *newrdn, const char *newparent, + int deleteoldrdn, LDAPControl *update_control, + const char *extop_oid, struct berval *extop_payload, char **retoidp, + struct berval **retdatap, LDAPControl ***returned_controls) +{ + int rc; + ConnResult return_value; + LDAPControl *server_controls[3]; + LDAPControl **loc_returned_controls; + const char *op_string = NULL; + const char *extra_op_string = NULL; + + server_controls[0] = &manageDSAITControl; + server_controls[1] = update_control; + server_controls[2] = NULL; + + if (conn_connected(conn)) + { + int msgid; + + conn->last_operation = optype; + switch (optype) + { + case CONN_ADD: + conn->status = STATUS_PROCESSING_ADD; + op_string = "add"; + rc = ldap_add_ext(conn->ld, dn, attrs, server_controls, + NULL /* clientctls */, &msgid); + break; + case CONN_MODIFY: + conn->status = STATUS_PROCESSING_MODIFY; + op_string = "modify"; + rc = ldap_modify_ext(conn->ld, dn, attrs, server_controls, + NULL /* clientctls */, &msgid); + break; + case CONN_DELETE: + conn->status = STATUS_PROCESSING_DELETE; + op_string = "delete"; + rc = ldap_delete_ext(conn->ld, dn, server_controls, + NULL /* clientctls */, &msgid); + break; + case CONN_RENAME: + conn->status = STATUS_PROCESSING_RENAME; + op_string = "rename"; + rc = ldap_rename(conn->ld, dn, newrdn, newparent, deleteoldrdn, + server_controls, NULL /* clientctls */, &msgid); + break; + case CONN_EXTENDED_OPERATION: + conn->status = STATUS_PROCESSING_EXTENDED_OPERATION; + op_string = "extended"; + extra_op_string = extop_oid; + rc = ldap_extended_operation(conn->ld, extop_oid, extop_payload, + server_controls, NULL /* clientctls */, &msgid); + } + if (LDAP_SUCCESS == rc) + { + LDAPMessage *res = NULL; + int setlevel = 0; + Slapi_Eq_Context eqctx = repl5_start_debug_timeout(&setlevel); + + rc = ldap_result(conn->ld, msgid, 1, &conn->timeout, &res); + repl5_stop_debug_timeout(eqctx, &setlevel); + if (0 == rc) + { + /* Timeout */ + rc = ldap_get_lderrno(conn->ld, NULL, NULL); + conn->last_ldap_error = LDAP_TIMEOUT; + return_value = CONN_TIMEOUT; + } + else if (-1 == rc) + { + /* Error */ + char *s = NULL; + + rc = ldap_get_lderrno(conn->ld, NULL, &s); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Received error %d: %s for %s operation\n", + agmt_get_long_name(conn->agmt), + rc, s ? s : "NULL", + op_string ? op_string : "NULL"); + conn->last_ldap_error = rc; + /* some errors will require a disconnect and retry the connection + later */ + if (IS_DISCONNECT_ERROR(rc)) + { + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + conn->status = STATUS_CONNECTED; + return_value = CONN_OPERATION_FAILED; + } + } + else + { + int err; + char *errmsg = NULL; + char **referrals = NULL; + char *matched = NULL; + + rc = ldap_parse_result(conn->ld, res, &err, &matched, + &errmsg, &referrals, &loc_returned_controls, + 0 /* Don't free the result */); + if (IS_DISCONNECT_ERROR(rc)) + { + conn->last_ldap_error = rc; + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else if (IS_DISCONNECT_ERROR(err)) + { + conn->last_ldap_error = err; + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + /* Got a result */ + else if (CONN_EXTENDED_OPERATION == optype) + { + if ((rc == LDAP_SUCCESS) && (err == LDAP_BUSY)) + return_value = CONN_BUSY; + else { + if (rc == LDAP_SUCCESS) { + rc = ldap_parse_extended_result(conn->ld, res, retoidp, + retdatap, 0 /* Don't Free it */); + } + conn->last_ldap_error = rc; + return_value = (LDAP_SUCCESS == conn->last_ldap_error ? + CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED); + } + } + else /* regular operation, result returned */ + { + if (NULL != returned_controls) + { + *returned_controls = loc_returned_controls; + } + if (LDAP_SUCCESS != rc) + { + conn->last_ldap_error = rc; + } + else + { + conn->last_ldap_error = err; + } + return_value = LDAP_SUCCESS == conn->last_ldap_error ? CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED; + } + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Received result code %d for %s operation %s%s\n", + agmt_get_long_name(conn->agmt), + conn->last_ldap_error, + op_string == NULL ? "" : op_string, + extra_op_string == NULL ? "" : extra_op_string, + extra_op_string == NULL ? "" : " "); + /* + * XXXggood do I need to free matched, referrals, + * anything else? Or can I pass NULL for the args + * I'm not interested in? + */ + /* Good question! Meanwhile, as RTM aproaches, let's free them... */ + slapi_ch_free((void **) &errmsg); + slapi_ch_free((void **) &matched); + charray_free(referrals); + conn->status = STATUS_CONNECTED; + } + if (res) ldap_msgfree(res); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Failed to send %s operation: LDAP error %d (%s)\n", + agmt_get_long_name(conn->agmt), + op_string ? op_string : "NULL", rc, ldap_err2string(rc)); + conn->last_ldap_error = rc; + if (IS_DISCONNECT_ERROR(rc)) + { + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + conn->status = STATUS_CONNECTED; + return_value = CONN_OPERATION_FAILED; + } + } + } + else + { + /* conn->last_ldap_error has been set to a more specific value + * in conn_connected() + * conn->last_ldap_error = LDAP_SERVER_DOWN; + */ + return_value = CONN_NOT_CONNECTED; + } + return return_value; +} + +/* + * Send an LDAP add operation. + */ +ConnResult +conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_ADD, dn, attrs, NULL /* newrdn */, + NULL /* newparent */, 0 /* deleteoldrdn */, update_control, + NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */, + NULL /* retdatap */, returned_controls); +} + + +/* + * Send an LDAP delete operation. + */ +ConnResult +conn_send_delete(Repl_Connection *conn, const char *dn, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_DELETE, dn, NULL /* attrs */, + NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */, + update_control, NULL /* extop OID */, NULL /* extop payload */, + NULL /* retoidp */, NULL /* retdatap */, returned_controls); +} + + +/* + * Send an LDAP modify operation. + */ +ConnResult +conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_MODIFY, dn, mods, NULL /* newrdn */, + NULL /* newparent */, 0 /* deleteoldrdn */, update_control, + NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */, + NULL /* retdatap */, returned_controls); +} + +/* + * Send an LDAP moddn operation. + */ +ConnResult +conn_send_rename(Repl_Connection *conn, const char *dn, + const char *newrdn, const char *newparent, int deleteoldrdn, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_RENAME, dn, NULL /* attrs */, + newrdn, newparent, deleteoldrdn, update_control, + NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */, + NULL /* retdatap */, returned_controls); +} + + +/* + * Send an LDAP extended operation. + */ +ConnResult +conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid, + struct berval *payload, char **retoidp, struct berval **retdatap, + LDAPControl *update_control, LDAPControl ***returned_controls) +{ + return perform_operation(conn, CONN_EXTENDED_OPERATION, NULL /* dn */, NULL /* attrs */, + NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */, + update_control, extop_oid, payload, retoidp, retdatap, + returned_controls); +} + + +/* + * Synchronously read an entry and return a specific attribute's values. + * Returns CONN_OPERATION_SUCCESS if successful. Returns + * CONN_OPERATION_FAILED if the operation was sent but an LDAP error + * occurred (conn->last_ldap_error is set in this case), and + * CONN_NOT_CONNECTED if no connection was active. + * + * The caller must free the returned_bvals. + */ +ConnResult +conn_read_entry_attribute(Repl_Connection *conn, const char *dn, + char *type, struct berval ***returned_bvals) +{ + ConnResult return_value; + int ldap_rc; + LDAPControl *server_controls[2]; + LDAPMessage *res = NULL; + char *attrs[2]; + + PR_ASSERT(NULL != type); + if (conn_connected(conn)) + { + server_controls[0] = &manageDSAITControl; + server_controls[1] = NULL; + attrs[0] = type; + attrs[1] = NULL; + ldap_rc = ldap_search_ext_s(conn->ld, dn, LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, 0 /* attrsonly */, + server_controls, NULL /* client controls */, + &conn->timeout, 0 /* sizelimit */, &res); + if (LDAP_SUCCESS == ldap_rc) + { + LDAPMessage *entry = ldap_first_entry(conn->ld, res); + if (NULL != entry) + { + *returned_bvals = ldap_get_values_len(conn->ld, entry, type); + } + return_value = CONN_OPERATION_SUCCESS; + } + else if (IS_DISCONNECT_ERROR(ldap_rc)) + { + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + return_value = CONN_OPERATION_FAILED; + } + conn->last_ldap_error = ldap_rc; + if (NULL != res) + { + ldap_msgfree(res); + res = NULL; + } + } + else + { + return_value = CONN_NOT_CONNECTED; + } + return return_value; +} + + +/* + * Return an pointer to a string describing the connection's status. +*/ + +const char * +conn_get_status(Repl_Connection *conn) +{ + return conn->status; +} + + + +/* + * Cancel any outstanding linger timer. Should be called when + * a replication session is beginning. + */ +void +conn_cancel_linger(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + if (conn->linger_active) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Cancelling linger on the connection\n", + agmt_get_long_name(conn->agmt)); + conn->linger_active = PR_FALSE; + if (slapi_eq_cancel(conn->linger_event) == 1) + { + conn->refcnt--; + } + conn->linger_event = NULL; + conn->status = STATUS_CONNECTED; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No linger to cancel on the connection\n", + agmt_get_long_name(conn->agmt)); + } + PR_Unlock(conn->lock); +} + + +/* + * Called when our linger timeout timer expires. This means + * we should check to see if perhaps the connection's become + * active again, in which case we do nothing. Otherwise, + * we close the connection. + */ +static void +linger_timeout(time_t event_time, void *arg) +{ + PRBool delete_now; + Repl_Connection *conn = (Repl_Connection *)arg; + + PR_ASSERT(NULL != conn); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Linger timeout has expired on the connection\n", + agmt_get_long_name(conn->agmt)); + PR_Lock(conn->lock); + if (conn->linger_active) + { + conn->linger_active = PR_FALSE; + conn->linger_event = NULL; + close_connection_internal(conn); + } + delete_now = conn->delete_after_linger; + PR_Unlock(conn->lock); + if (delete_now) + { + conn_delete_internal(conn); + } +} + + +/* + * Indicate that a session is ending. The linger timer starts when + * this function is called. + */ +void +conn_start_linger(Repl_Connection *conn) +{ + time_t now; + + PR_ASSERT(NULL != conn); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Beginning linger on the connection\n", + agmt_get_long_name(conn->agmt)); + if (!conn_connected(conn)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No linger on the closed conn\n", + agmt_get_long_name(conn->agmt)); + return; + } + time(&now); + PR_Lock(conn->lock); + if (conn->linger_active) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Linger already active on the connection\n", + agmt_get_long_name(conn->agmt)); + } + else + { + conn->linger_active = PR_TRUE; + conn->linger_event = slapi_eq_once(linger_timeout, conn, now + conn->linger_time); + conn->status = STATUS_LINGERING; + } + PR_Unlock(conn->lock); +} + + + +/* + * If no connection is currently active, opens a connection and binds to + * the remote server. If a connection is open (e.g. lingering) then + * this is a no-op. + * + * Returns CONN_OPERATION_SUCCESS on success, or CONN_OPERATION_FAILED + * on failure. Sets conn->last_ldap_error and conn->last_operation; + */ +ConnResult +conn_connect(Repl_Connection *conn) +{ + int ldap_rc; + int optdata; + int secure = 0; + char* binddn = NULL; + struct berval *creds; + ConnResult return_value = CONN_OPERATION_SUCCESS; + int pw_ret = 1; + + /** Connection already open just return SUCCESS **/ + if(conn->state == STATE_CONNECTED) return return_value; + + PR_Lock(conn->lock); + if (conn->flag_agmt_changed) { + /* So far we cannot change Hostname and Port */ + /* slapi_ch_free((void **)&conn->hostname); */ + /* conn->hostname = agmt_get_hostname(conn->agmt); */ + /* conn->port = agmt_get_port(conn->agmt); */ + slapi_ch_free((void **)&conn->binddn); + conn->binddn = agmt_get_binddn(conn->agmt); + conn->bindmethod = agmt_get_bindmethod(conn->agmt); + conn->transport_flags = agmt_get_transport_flags(conn->agmt); + conn->timeout.tv_sec = agmt_get_timeout(conn->agmt); + conn->flag_agmt_changed = 0; + slapi_ch_free((void **)&conn->plain); + } + PR_Unlock(conn->lock); + + creds = agmt_get_credentials(conn->agmt); + + if (conn->plain == NULL) { + + char *plain = NULL; + + /* kexcoff: for reversible encryption */ + /* We need to test the return code of pw_rever_decode in order to decide + * if a free for plain will be needed (pw_ret == 0) or not (pw_ret != 0) */ + pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaCredentials); + /* Pb occured in decryption: stop now, binding will fail */ + if ( pw_ret == -1 ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Decoding of the credentials failed.\n", + agmt_get_long_name(conn->agmt)); + + return_value = CONN_OPERATION_FAILED; + conn->last_ldap_error = LDAP_INVALID_CREDENTIALS; + conn->state = STATE_DISCONNECTED; + return (return_value); + } /* Else, does not mean that the plain is correct, only means the we had no internal + decoding pb */ + conn->plain = slapi_ch_strdup (plain); + if (!pw_ret) slapi_ch_free((void**)&plain); + } + + + /* ugaston: if SSL has been selected in the replication agreement, SSL client + * initialisation should be done before ever trying to open any connection at all. + */ + if (conn->transport_flags == TRANSPORT_FLAG_TLS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication secured by StartTLS not currently supported\n", + agmt_get_long_name(conn->agmt)); + + return_value = CONN_OPERATION_FAILED; + conn->last_ldap_error = LDAP_STRONG_AUTH_NOT_SUPPORTED; + conn->state = STATE_DISCONNECTED; + } else if(conn->transport_flags == TRANSPORT_FLAG_SSL) + { + + /** Make sure the SSL Library has been initialized before anything else **/ + if(slapd_security_library_is_initialized() != 1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: SSL Not Initialized, Replication over SSL FAILED\n", + agmt_get_long_name(conn->agmt)); + conn->last_ldap_error = LDAP_INAPPROPRIATE_AUTH; + conn->last_operation = CONN_INIT; + ber_bvfree(creds); + creds = NULL; + return CONN_SSL_NOT_ENABLED; + } else + { + secure = 1; + } + } + + if (return_value == CONN_OPERATION_SUCCESS) { + int io_timeout_ms; + /* Now we initialize the LDAP Structure and set options */ + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Trying %s slapi_ldap_init\n", + agmt_get_long_name(conn->agmt), + secure ? "secure" : "non-secure"); + + conn->ld = slapi_ldap_init(conn->hostname, conn->port, secure, 0); + if (NULL == conn->ld) + { + return_value = CONN_OPERATION_FAILED; + conn->state = STATE_DISCONNECTED; + conn->last_operation = CONN_INIT; + conn->last_ldap_error = LDAP_LOCAL_ERROR; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Failed to establish %sconnection to the consumer\n", + agmt_get_long_name(conn->agmt), + secure ? "secure " : ""); + ber_bvfree(creds); + creds = NULL; + return return_value; + } + + /* slapi_ch_strdup is OK with NULL strings */ + binddn = slapi_ch_strdup(conn->binddn); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: binddn = %s, passwd = %s\n", + agmt_get_long_name(conn->agmt), + binddn?binddn:"NULL", creds->bv_val?creds->bv_val:"NULL"); + + /* Set some options for the connection. */ + optdata = LDAP_DEREF_NEVER; /* Don't dereference aliases */ + ldap_set_option(conn->ld, LDAP_OPT_DEREF, &optdata); + + optdata = LDAP_VERSION3; /* We need LDAP version 3 */ + ldap_set_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, &optdata); + + /* Don't chase any referrals (although we shouldn't get any) */ + ldap_set_option(conn->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); + + /* override the default timeout with the specified timeout */ + io_timeout_ms = conn->timeout.tv_sec * 1000 + conn->timeout.tv_usec / 1000; + prldap_set_session_option(conn->ld, NULL, PRLDAP_OPT_IO_MAX_TIMEOUT, + io_timeout_ms); + + /* We've got an ld. Now bind to the server. */ + conn->last_operation = CONN_BIND; + + } + + if ( bind_and_check_pwp(conn, binddn, conn->plain) == CONN_OPERATION_FAILED ) + { + conn->last_ldap_error = ldap_get_lderrno (conn->ld, NULL, NULL); + conn->state = STATE_DISCONNECTED; + return_value = CONN_OPERATION_FAILED; + } + else + { + conn->last_ldap_error = ldap_rc = LDAP_SUCCESS; + conn->state = STATE_CONNECTED; + return_value = CONN_OPERATION_SUCCESS; + } + + + ber_bvfree(creds); + creds = NULL; + + slapi_ch_free((void**)&binddn); + + if(return_value == CONN_OPERATION_FAILED) + { + close_connection_internal(conn); + } else + { + conn->last_ldap_error = ldap_rc = LDAP_SUCCESS; + conn->state = STATE_CONNECTED; + } + + return return_value; +} + + +static void +close_connection_internal(Repl_Connection *conn) +{ + if (NULL != conn->ld) + { + /* Since we call slapi_ldap_init, + we must call slapi_ldap_unbind */ + slapi_ldap_unbind(conn->ld); + } + conn->ld = NULL; + conn->state = STATE_DISCONNECTED; + conn->status = STATUS_DISCONNECTED; + conn->supports_ds50_repl = -1; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Disconnected from the consumer\n", agmt_get_long_name(conn->agmt)); +} + +void +conn_disconnect(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + close_connection_internal(conn); + PR_Unlock(conn->lock); +} + + +/* + * Determine if the remote replica supports DS 5.0 replication. + * Return codes: + * CONN_SUPPORTS_DS5_REPL - the remote replica suport DS5 replication + * CONN_DOES_NOT_SUPPORT_DS5_REPL - the remote replica does not + * support DS5 replication. + * CONN_OPERATION_FAILED - it could not be determined if the remote + * replica supports DS5 replication. + * CONN_NOT_CONNECTED - no connection was active. + */ +ConnResult +conn_replica_supports_ds5_repl(Repl_Connection *conn) +{ + ConnResult return_value; + int ldap_rc; + + if (conn_connected(conn)) + { + if (conn->supports_ds50_repl == -1) { + LDAPMessage *res = NULL; + LDAPMessage *entry = NULL; + char *attrs[] = {"supportedcontrol", "supportedextension", NULL}; + + conn->status = STATUS_SEARCHING; + ldap_rc = ldap_search_ext_s(conn->ld, "", LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, 0 /* attrsonly */, + NULL /* server controls */, NULL /* client controls */, + &conn->timeout, LDAP_NO_LIMIT, &res); + if (LDAP_SUCCESS == ldap_rc) + { + conn->supports_ds50_repl = 0; + entry = ldap_first_entry(conn->ld, res); + if (!attribute_string_value_present(conn->ld, entry, "supportedcontrol", REPL_NSDS50_UPDATE_INFO_CONTROL_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_START_NSDS50_REPLICATION_REQUEST_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_END_NSDS50_REPLICATION_REQUEST_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_RESPONSE_OID)) + { + return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + else + { + conn->supports_ds50_repl = 1; + return_value = CONN_SUPPORTS_DS5_REPL; + } + } + else + { + if (IS_DISCONNECT_ERROR(ldap_rc)) + { + conn->last_ldap_error = ldap_rc; /* specific reason */ + conn_disconnect(conn); + return_value = CONN_NOT_CONNECTED; + } + else + { + return_value = CONN_OPERATION_FAILED; + } + } + if (NULL != res) + ldap_msgfree(res); + } + else { + return_value = conn->supports_ds50_repl ? CONN_SUPPORTS_DS5_REPL : CONN_DOES_NOT_SUPPORT_DS5_REPL; + } + } + else + { + /* Not connected */ + return_value = CONN_NOT_CONNECTED; + } + return return_value; +} + + + + + +/* + * Return 1 if "value" is a value of attribute type "type" in entry "entry". + * Otherwise, return 0. + */ +static int +attribute_string_value_present(LDAP *ld, LDAPMessage *entry, const char *type, + const char *value) +{ + int return_value = 0; + + if (NULL != entry) + { + char *atype = NULL; + BerElement *ber = NULL; + + atype = ldap_first_attribute(ld, entry, &ber); + while (NULL != atype && 0 == return_value) + { + if (strcasecmp(atype, type) == 0) + { + char **strvals = ldap_get_values(ld, entry, atype); + int i; + for (i = 0; return_value == 0 && NULL != strvals && NULL != strvals[i]; i++) + { + if (strcmp(strvals[i], value) == 0) + { + return_value = 1; + } + } + if (NULL != strvals) + { + ldap_value_free(strvals); + } + } + ldap_memfree(atype); + atype = ldap_next_attribute(ld, entry, ber); + } + if (NULL != ber) + ldap_ber_free(ber, 0); + /* The last atype has not been freed yet */ + if (NULL != atype) + ldap_memfree(atype); + } + return return_value; +} + + + + +/* + * Read the remote server's schema entry, then read the local schema entry, + * and compare the nsschemacsn attribute. If the local csn is newer, or + * the remote csn is absent, push the schema down to the consumer. + * Return codes: + * CONN_SCHEMA_UPDATED if the schema was pushed successfully + * CONN_SCHEMA_NO_UPDATE_NEEDED if the schema was as new or newer than + * the local server's schema + * CONN_OPERATION_FAILED if an error occurred + * CONN_NOT_CONNECTED if no connection was active + * NOTE: Should only be called when a replication session has been + * established by sending a startReplication extended operation. + */ +ConnResult +conn_push_schema(Repl_Connection *conn, CSN **remotecsn) +{ + ConnResult return_value = CONN_OPERATION_SUCCESS; + char *nsschemacsn = "nsschemacsn"; + Slapi_Entry **entries = NULL; + Slapi_Entry *schema_entry = NULL; + int push_schema = 1; /* Assume we need to push for now */ + int local_error = 0; /* No local error encountered yet */ + int remote_error = 0; /* No remote error encountered yet */ + CSN *localcsn = NULL; + Slapi_PBlock *spb = NULL; + char localcsnstr[CSN_STRSIZE + 1] = {0}; + + if (!conn_connected(conn)) + { + return_value = CONN_NOT_CONNECTED; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Schema replication update failed: not connected to consumer\n", + agmt_get_long_name(conn->agmt)); + } + else + { + localcsn = dup_global_schema_csn(); + if (NULL == localcsn) + { + /* Local server has epoch CSN, so don't push schema */ + return_value = CONN_SCHEMA_NO_UPDATE_NEEDED; + } + else if ( remotecsn && *remotecsn && csn_compare(localcsn, *remotecsn) <= 0 ) + { + /* Local server schema is not newer than the remote one */ + return_value = CONN_SCHEMA_NO_UPDATE_NEEDED; + } + else + { + struct berval **remote_schema_csn_bervals = NULL; + /* Get remote server's schema */ + return_value = conn_read_entry_attribute(conn, "cn=schema", nsschemacsn, + &remote_schema_csn_bervals); + if (CONN_OPERATION_SUCCESS == return_value) + { + if (NULL != remote_schema_csn_bervals && NULL != remote_schema_csn_bervals[0]) + { + char remotecsnstr[CSN_STRSIZE + 1] = {0}; + memcpy(remotecsnstr, remote_schema_csn_bervals[0]->bv_val, + remote_schema_csn_bervals[0]->bv_len); + remotecsnstr[remote_schema_csn_bervals[0]->bv_len] = '\0'; + *remotecsn = csn_new_by_string(remotecsnstr); + if (NULL != remotecsn && (csn_compare(localcsn, *remotecsn) <= 0)) + { + return_value = CONN_SCHEMA_NO_UPDATE_NEEDED; + } + /* Need to free the remote_schema_csn_bervals */ + ber_bvecfree(remote_schema_csn_bervals); + } + } + } + } + if (CONN_OPERATION_SUCCESS == return_value) + { + /* We know we need to push the schema out. */ + LDAPMod ocmod = {0}; + LDAPMod atmod = {0}; + LDAPMod csnmod = {0}; + LDAPMod *attrs[4] = {0}; + int numvalues = 0; + Slapi_Attr *attr = NULL; + char *csnvalues[2]; + + ocmod.mod_type = "objectclasses"; + ocmod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES; + ocmod.mod_bvalues = NULL; + atmod.mod_type = "attributetypes"; + atmod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES; + atmod.mod_bvalues = NULL; + csnmod.mod_type = nsschemacsn; + csnmod.mod_op = LDAP_MOD_REPLACE; + csn_as_string (localcsn, PR_FALSE, localcsnstr); + csnvalues[0] = localcsnstr; + csnvalues[1] = NULL; + csnmod.mod_values = csnvalues; + attrs[0] = &ocmod; + attrs[1] = &atmod; + attrs[2] = &csnmod; + attrs[3] = NULL; + + return_value = CONN_OPERATION_FAILED; /* assume failure */ + + /* Get local schema */ + spb = slapi_search_internal("cn=schema", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL /* controls */, NULL /* schema_csn_attrs */, 0 /* attrsonly */); + slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || NULL == entries[0]) + { + /* Whoops - couldn't read our own schema! */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Error: unable to read local schema definitions.\n", + agmt_get_long_name(conn->agmt)); + return_value = CONN_OPERATION_FAILED; + } + else + { + schema_entry = entries[0]; + if (slapi_entry_attr_find(schema_entry, "objectclasses", &attr) != -1) + { + int i, ind; + Slapi_Value *value; + slapi_attr_get_numvalues(attr, &numvalues); + ocmod.mod_bvalues = (struct berval **)slapi_ch_malloc((numvalues + 1) * + sizeof(struct berval *)); + for (i = 0, ind = slapi_attr_first_value(attr, &value); + ind != -1; ind = slapi_attr_next_value(attr, ind, &value), i++) + { + /* XXXggood had to cast away const below */ + ocmod.mod_bvalues[i] = (struct berval *)slapi_value_get_berval(value); + } + ocmod.mod_bvalues[numvalues] = NULL; + if (slapi_entry_attr_find(schema_entry, "attributetypes", &attr) != -1) + { + ConnResult result; + slapi_attr_get_numvalues(attr, &numvalues); + atmod.mod_bvalues = (struct berval **)slapi_ch_malloc((numvalues + 1) * + sizeof(struct berval *)); + for (i = 0, ind = slapi_attr_first_value(attr, &value); + ind != -1; ind = slapi_attr_next_value(attr, ind, &value), i++) + { + /* XXXggood had to cast away const below */ + atmod.mod_bvalues[i] = (struct berval *)slapi_value_get_berval(value); + } + atmod.mod_bvalues[numvalues] = NULL; + + result = conn_send_modify(conn, "cn=schema", attrs, NULL, NULL); + switch (result) + { + case CONN_OPERATION_FAILED: + { + int ldaperr = -1, optype = -1; + conn_get_error(conn, &optype, &ldaperr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Schema replication update failed: %s\n", + agmt_get_long_name(conn->agmt), + ldaperr == -1 ? "Unknown Error" : ldap_err2string(ldaperr)); + } + case CONN_NOT_CONNECTED: + return_value = CONN_NOT_CONNECTED; + break; + case CONN_OPERATION_SUCCESS: + return_value = CONN_SCHEMA_UPDATED; + break; + } + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Schema replication update failed: " + "unable to prepare schema entry for transmission.\n", + agmt_get_long_name(conn->agmt)); + } + } + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&ocmod.mod_bvalues); + slapi_ch_free((void **)&atmod.mod_bvalues); + } + if (NULL != spb) + { + slapi_free_search_results_internal(spb); + slapi_pblock_destroy(spb); + spb = NULL; + } + if (NULL != localcsn) + { + csn_free(&localcsn); + } + return return_value; +} + +void +conn_set_timeout(Repl_Connection *conn, long timeout) +{ + PR_ASSERT(NULL != conn); + PR_ASSERT(timeout >= 0); + PR_Lock(conn->lock); + conn->timeout.tv_sec = timeout; + PR_Unlock(conn->lock); +} + +void conn_set_agmt_changed(Repl_Connection *conn) +{ + PR_ASSERT(NULL != conn); + PR_Lock(conn->lock); + if (NULL != conn->agmt) + conn->flag_agmt_changed = 1; + PR_Unlock(conn->lock); +} + +/* + * Check the result of an ldap_simple_bind operation to see we it + * contains the expiration controls + * return: -1 error, not bound + * 0, OK bind has succeeded + */ +static int +bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password) +{ + + LDAPControl **ctrls = NULL; + LDAPMessage *res = NULL; + char *errmsg = NULL; + LDAP *ld = conn->ld; + int msgid; + int *msgidAdr = &msgid; + int rc; + + char * optype; /* ldap_simple_bind or slapd_SSL_client_bind */ + + if ( conn->transport_flags == TRANSPORT_FLAG_SSL ) + { + char *auth; + optype = "ldap_sasl_bind"; + + if ( conn->bindmethod == BINDMETHOD_SSL_CLIENTAUTH ) + { + rc = slapd_sasl_ext_client_bind(conn->ld, &msgidAdr); + auth = "SSL client authentication"; + + if ( rc == LDAP_SUCCESS ) + { + if (conn->last_ldap_error != rc) + { + conn->last_ldap_error = rc; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication bind with %s resumed\n", + agmt_get_long_name(conn->agmt), auth); + } + } + else + { + /* Do not report the same error over and over again */ + if (conn->last_ldap_error != rc) + { + conn->last_ldap_error = rc; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication bind with %s failed: LDAP error %d (%s)\n", + agmt_get_long_name(conn->agmt), auth, rc, + ldap_err2string(rc)); + } + + return (CONN_OPERATION_FAILED); + } + } + else + { + if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 ) + { + return (CONN_OPERATION_FAILED); + } + } + } + else + { + optype = "ldap_simple_bind"; + if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 ) + { + return (CONN_OPERATION_FAILED); + } + } + + /* Wait for the result */ + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &res ) == -1 ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Received error from consumer for %s operation\n", + agmt_get_long_name(conn->agmt), optype); + + return (CONN_OPERATION_FAILED); + } + /* Don't check ldap_result against 0 because, no timeout is specified */ + + /* Free res as we won't use it any longer */ + if ( ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, &ctrls, 1 /* Free res */) + != LDAP_SUCCESS ) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Received error from consumer for %s operation\n", + agmt_get_long_name(conn->agmt), optype); + + return (CONN_OPERATION_FAILED); + } + + if ( rc == LDAP_SUCCESS ) + { + if ( ctrls ) + { + int i; + for( i = 0; ctrls[ i ] != NULL; ++i ) + { + if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRED)) ) + { + /* Bind is successfull but password has expired */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Succesfully bound %s to consumer, " + "but password has expired on consumer.\n", + agmt_get_long_name(conn->agmt), binddn); + } + else if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRING)) ) + { + /* The password is expiring in n seconds */ + if ( (ctrls[ i ]->ldctl_value.bv_val != NULL) && + (ctrls[ i ]->ldctl_value.bv_len > 0) ) + { + int password_expiring = atoi( ctrls[ i ]->ldctl_value.bv_val ); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Succesfully bound %s to consumer, " + "but password is expiring on consumer in %d seconds.\n", + agmt_get_long_name(conn->agmt), binddn, password_expiring); + } + } + } + ldap_controls_free( ctrls ); + } + + return (CONN_OPERATION_SUCCESS); + } + else + { + /* errmsg is a pointer directly into the ld structure - do not free */ + rc = ldap_get_lderrno( ld, NULL, &errmsg ); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replication bind to %s on consumer failed: %d (%s)\n", + agmt_get_long_name(conn->agmt), binddn, rc, errmsg); + + conn->last_ldap_error = rc; /* specific error */ + return (CONN_OPERATION_FAILED); + } +} + +static int +do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password) +{ + int msgid; + + if( ( msgid = ldap_simple_bind( ld, binddn, password ) ) == -1 ) + { + char *ldaperrtext = NULL; + int ldaperr; + int prerr = PR_GetError(); + + ldaperr = ldap_get_lderrno( ld, NULL, &ldaperrtext ); + /* Do not report the same error over and over again */ + if (conn->last_ldap_error != ldaperr) + { + conn->last_ldap_error = ldaperr; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Simple bind failed, " + SLAPI_COMPONENT_NAME_LDAPSDK " error %d (%s), " + SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n", + agmt_get_long_name(conn->agmt), + ldaperr, ldaperrtext ? ldaperrtext : ldap_err2string(ldaperr), + prerr, slapd_pr_strerror(prerr)); + } + } + else if (conn->last_ldap_error != LDAP_SUCCESS) + { + conn->last_ldap_error = LDAP_SUCCESS; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Simple bind resumed\n", + agmt_get_long_name(conn->agmt)); + } + return msgid; +} + +void +repl5_set_debug_timeout(const char *val) +{ + /* val looks like this: seconds[:debuglevel] */ + /* seconds is the number of seconds to wait until turning on the debug level */ + /* this should be less than the ldap connection timeout (default 10 minutes) */ + /* the optional debug level is the error log debugging level to use (default repl) */ + if (val) { + const char *p = strchr(val, ':'); + s_debug_timeout = atoi(val); + if (p) { + s_debug_level = atoi(p+1); + } else { + s_debug_level = 8192; + } + } +} + +static time_t +PRTime2time_t (PRTime tm) +{ + PRInt64 rt; + + PR_ASSERT (tm); + + LL_DIV(rt, tm, PR_USEC_PER_SEC); + + return (time_t)rt; +} + +static Slapi_Eq_Context +repl5_start_debug_timeout(int *setlevel) +{ + Slapi_Eq_Context eqctx = 0; + if (s_debug_timeout && s_debug_level) { + time_t now = time(NULL); + eqctx = slapi_eq_once(repl5_debug_timeout_callback, setlevel, + s_debug_timeout + now); + } + return eqctx; +} + +static void +repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel) +{ + char buf[20]; + char msg[SLAPI_DSE_RETURNTEXT_SIZE]; + + if (eqctx && !*setlevel) { + int found = slapi_eq_cancel(eqctx); + } + + if (s_debug_timeout && s_debug_level && *setlevel) { + void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply); + sprintf(buf, "%d", 0); + config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1); + } +} + +static void +repl5_debug_timeout_callback(time_t when, void *arg) +{ + int *setlevel = (int *)arg; + void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply); + char buf[20]; + char msg[SLAPI_DSE_RETURNTEXT_SIZE]; + + *setlevel = 1; + sprintf(buf, "%d", s_debug_level); + config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "repl5_debug_timeout_callback: set debug level to %d at %d\n", + s_debug_level, when); +} diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c new file mode 100644 index 00000000..a9905a34 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c @@ -0,0 +1,1759 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_inc_protocol.c */ +/* + + The Prot_Incremental object implements the DS 5.0 multi-master incremental + replication protocol. + + +Stuff to do: + +- Need to figure out how asynchronous events end up in here. They are: + - entry updated in replicated area. + - backoff timeout + - enter/leave. + +Perhaps these events should be properties of the main protocol. +*/ + +#include "repl.h" +#include "repl5.h" +#include "repl5_ruv.h" +#include "repl5_prot_private.h" +#include "cl5_api.h" + +extern int slapi_log_urp; + +/*** from proto-slap.h ***/ +void ava_done(struct ava *ava); + +typedef struct repl5_inc_private +{ + char *ruv; /* RUV on remote replica (use diff type for this? - ggood */ + Backoff_Timer *backoff; + Repl_Protocol *rp; + PRLock *lock; + PRUint32 eventbits; +} repl5_inc_private; + + +/* Various states the incremental protocol can pass through */ +#define STATE_START 0 /* ONREPL - should we rename this - we don't use it just to start up? */ +#define STATE_WAIT_WINDOW_OPEN 1 +#define STATE_WAIT_CHANGES 2 +#define STATE_READY_TO_ACQUIRE 3 +#define STATE_BACKOFF_START 4 /* ONREPL - can we combine BACKOFF_START and BACKOFF states? */ +#define STATE_BACKOFF 5 +#define STATE_SENDING_UPDATES 6 +#define STATE_STOP_FATAL_ERROR 7 +#define STATE_STOP_FATAL_ERROR_PART2 8 +#define STATE_STOP_NORMAL_TERMINATION 9 + +/* Events (synchronous and asynchronous; these are bits) */ +#define EVENT_WINDOW_OPENED 1 +#define EVENT_WINDOW_CLOSED 2 +#define EVENT_TRIGGERING_CRITERIA_MET 4 /* ONREPL - should we rename this to EVENT_CHANGE_AVAILABLE */ +#define EVENT_BACKOFF_EXPIRED 8 +#define EVENT_REPLICATE_NOW 16 +#define EVENT_PROTOCOL_SHUTDOWN 32 +#define EVENT_AGMT_CHANGED 64 + +#define UPDATE_NO_MORE_UPDATES 201 +#define UPDATE_TRANSIENT_ERROR 202 +#define UPDATE_FATAL_ERROR 203 +#define UPDATE_SCHEDULE_WINDOW_CLOSED 204 +#define UPDATE_CONNECTION_LOST 205 +#define UPDATE_TIMEOUT 206 +#define UPDATE_YIELD 207 + +/* Return codes from examine_update_vector */ +#define EXAMINE_RUV_PRISTINE_REPLICA 401 +#define EXAMINE_RUV_GENERATION_MISMATCH 402 +#define EXAMINE_RUV_REPLICA_TOO_OLD 403 +#define EXAMINE_RUV_OK 404 +#define EXAMINE_RUV_PARAM_ERROR 405 + +#define MAX_CHANGES_PER_SESSION 10000 +/* + * Maximum time to wait between replication sessions. If we + * don't see any updates for a period equal to this interval, + * we go ahead and start a replication session, just to be safe + */ +#define MAX_WAIT_BETWEEN_SESSIONS PR_SecondsToInterval(60 * 5) /* 5 minutes */ + +/* + * tests if the protocol has been shutdown and we need to quit + * event_occurred resets the bits in the bit flag, so whoever tests for shutdown + * resets the flags, so the next one who tests for shutdown won't get it, so we + * also look at the terminate flag + */ +#define PROTOCOL_IS_SHUTDOWN(prp) (event_occurred(prp, EVENT_PROTOCOL_SHUTDOWN) || prp->terminate) + +/* Forward declarations */ +static PRUint32 event_occurred(Private_Repl_Protocol *prp, PRUint32 event); +static void reset_events (Private_Repl_Protocol *prp); +static void protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration); +static int send_updates(Private_Repl_Protocol *prp, RUV *ruv, PRUint32 *num_changes_sent); +static void repl5_inc_backoff_expired(time_t timer_fire_time, void *arg); +static int examine_update_vector(Private_Repl_Protocol *prp, RUV *ruv); +static PRBool ignore_error_and_keep_going(int error); +static const char* state2name (int state); +static const char* event2name (int event); +static const char* op2string (int op); + +/* + * It's specifically ok to delete a protocol instance that + * is currently running. The instance will be shut down, and + * then resources will be freed. Since a graceful shutdown is + * attempted, this function may take some time to complete. + */ +static void +repl5_inc_delete(Private_Repl_Protocol **prpp) +{ + /* First, stop the protocol if it isn't already stopped */ + /* Then, delete all resources used by the protocol */ +} + +/* helper function */ +void +set_pause_and_busy_time(long *pausetime, long *busywaittime) +{ + /* If neither are set, set busy time to its default */ + if (!*pausetime && !*busywaittime) + { + *busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM; + } + /* pause time must be at least 1 more than the busy backoff time */ + if (*pausetime && !*busywaittime) + { + /* + * user specified a pause time but no busy wait time - must + * set busy wait time to 1 less than pause time - if pause + * time is 1, we must set it to 2 + */ + if (*pausetime < 2) + { + *pausetime = 2; + } + *busywaittime = *pausetime - 1; + } + else if (!*pausetime && *busywaittime) + { + /* + * user specified a busy wait time but no pause time - must + * set pause time to 1 more than busy wait time + */ + *pausetime = *busywaittime + 1; + } + else if (*pausetime && *busywaittime && *pausetime <= *busywaittime) + { + /* + * user specified both pause and busy wait times, but the pause + * time was <= busy wait time - pause time must be at least + * 1 more than the busy wait time + */ + *pausetime = *busywaittime + 1; + } +} + +/* + * Do the incremental protocol. + * + * What's going on here? This thing is a state machine. It has the + * following states: + * + * State transition table: + * + * Curr State Condition/Event Next State + * ---------- ------------ ----------- + * START schedule window is open ACQUIRE_REPLICA + * schedule window is closed WAIT_WINDOW_OPEN + * WAIT_WINDOW_OPEN schedule change START + * replicate now ACQUIRE_REPLICA + * schedule window opens ACQUIRE_REPLICA + * ACQUIRE_REPLICA acquired replica SEND_CHANGES + * failed to acquire - transient error START_BACKOFF + * failed to acquire - fatal error STOP_FATAL_ERROR + * SEND_CHANGES can't update CONSUMER_NEEDS_REINIT + * no changes to send WAIT_CHANGES + * can't send - thransient error START_BACKOF + * can't send - window closed WAIT_WINDOW_OPEN + * can'r send - fatal error STOP_FATAL_ERROR + * START_BACKOF replicate now ACQUIRE_REPLICA + * schedule changes START + * schedule window closes WAIT_WINDOW_OPEN + * backoff expires & can acquire SEND_CHANGES + * backoff expires & can't acquire-trans BACKOFF + * backoff expires & can't acquire-fatal STOP_FATAL_ERROR + * BACKOF replicate now ACQUIRE_REPLICA + * schedule changes START + * schedule window closes WAIT_WINDOW_OPEN + * backoff expires & can acquire SEND_CHANGES + * backoff expires & can't acquire-trans BACKOFF + * backoff expires & can't acquire-fatal STOP_FATAL_ERROR + * WAIT_CHANGES schedule window closes WAIT_WINDOW_OPEN + * replicate_now ACQUIRE_REPLICA + * change available ACQUIRE_REPLICA + * schedule_change START + */ + +/* + * Main state machine for the incremental protocol. This routine will, + * under normal circumstances, not return until the protocol is shut + * down. + */ +static void +repl5_inc_run(Private_Repl_Protocol *prp) +{ + int current_state = STATE_START; + int next_state = STATE_START; + repl5_inc_private *prp_priv = (repl5_inc_private *)prp->private; + int done; + int e1; + RUV *ruv = NULL; + CSN *cons_schema_csn; + Replica *replica; + int wait_change_timer_set = 0; + time_t last_start_time; + PRUint32 num_changes_sent; + char *hostname = NULL; + int portnum = 0; + /* use a different backoff timer strategy for ACQUIRE_REPLICA_BUSY errors */ + PRBool use_busy_backoff_timer = PR_FALSE; + long pausetime = 0; + long busywaittime = 0; + + prp->stopped = 0; + prp->terminate = 0; + hostname = agmt_get_hostname(prp->agmt); + portnum = agmt_get_port(prp->agmt); + + /* establish_protocol_callbacks(prp); */ + done = 0; + do { + int rc; + + /* Take action, based on current state, and compute new state. */ + switch (current_state) + { + case STATE_START: + + dev_debug("repl5_inc_run(STATE_START)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + + /* + * Our initial state. See if we're in a schedule window. If + * so, then we're ready to acquire the replica and see if it + * needs any updates from us. If not, then wait for the window + * to open. + */ + if (agmt_schedule_in_window_now(prp->agmt)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else + { + next_state = STATE_WAIT_WINDOW_OPEN; + } + + /* we can get here from other states because some events happened and were + not cleared. For instance when we wake up in STATE_WAIT_CHANGES state. + Since this is a fresh start state, we should clear all events */ + /* ONREPL - this does not feel right - we should take another look + at this state machine */ + reset_events (prp); + + /* Cancel any linger timer that might be in effect... */ + conn_cancel_linger(prp->conn); + /* ... and disconnect, if currently connected */ + conn_disconnect(prp->conn); + /* get the new pause time, if any */ + pausetime = agmt_get_pausetime(prp->agmt); + /* get the new busy wait time, if any */ + busywaittime = agmt_get_busywaittime(prp->agmt); + if (pausetime || busywaittime) + { + /* helper function to make sure they are set correctly */ + set_pause_and_busy_time(&pausetime, &busywaittime); + } + break; + case STATE_WAIT_WINDOW_OPEN: + /* + * We're waiting for a schedule window to open. If one did, + * or we receive a "replicate now" event, then start a protocol + * session immediately. If the replication schedule changed, go + * back to start. Otherwise, go back to sleep. + */ + dev_debug("repl5_inc_run(STATE_WAIT_WINDOW_OPEN)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + else if (event_occurred(prp, EVENT_WINDOW_OPENED)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + next_state = STATE_START; + conn_set_agmt_changed(prp->conn); + } + else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) /* change available */ + { + /* just ignore it and go to sleep */ + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + else if (e1 = event_occurred(prp, EVENT_WINDOW_CLOSED) || + event_occurred(prp, EVENT_BACKOFF_EXPIRED)) + { + /* this events - should not occur - log a warning and go to sleep */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental protocol: " + "event %s should not occur in state %s; going to sleep\n", + agmt_get_long_name(prp->agmt), + e1 ? event2name(EVENT_WINDOW_CLOSED) : event2name(EVENT_BACKOFF_EXPIRED), + state2name(current_state)); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + else + { + /* wait until window opens or an event occurs */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Incremental protocol: " + "waiting for update window to open\n", agmt_get_long_name(prp->agmt)); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + break; + case STATE_WAIT_CHANGES: + /* + * We're in a replication window, but we're waiting for more + * changes to accumulate before we actually hook up and send + * them. + */ + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): PROTOCOL_IS_SHUTING_DOWN -> end repl5_inc_run\n"); + done = 1; + break; + } + else if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_REPLICATE_NOW received -> STATE_READY_TO_ACQUIRE\n"); + next_state = STATE_READY_TO_ACQUIRE; + wait_change_timer_set = 0; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_AGMT_CHANGED received -> STATE_START\n"); + next_state = STATE_START; + conn_set_agmt_changed(prp->conn); + wait_change_timer_set = 0; + } + else if (event_occurred(prp, EVENT_WINDOW_CLOSED)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_WINDOW_CLOSED received -> STATE_WAIT_WINDOW_OPEN\n"); + next_state = STATE_WAIT_WINDOW_OPEN; + wait_change_timer_set = 0; + } + else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) + { + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): EVENT_TRIGGERING_CRITERIA_MET received -> STATE_READY_TO_ACQUIRE\n"); + next_state = STATE_READY_TO_ACQUIRE; + wait_change_timer_set = 0; + } + else if (e1 = event_occurred(prp, EVENT_WINDOW_OPENED) || + event_occurred(prp, EVENT_BACKOFF_EXPIRED)) + { + /* this events - should not occur - log a warning and clear the event */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: Incremental protocol: " + "event %s should not occur in state %s\n", + agmt_get_long_name(prp->agmt), + e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED), + state2name(current_state)); + wait_change_timer_set = 0; + } + else + { + if (wait_change_timer_set) + { + /* We are here because our timer expired */ + dev_debug("repl5_inc_run(STATE_WAIT_CHANGES): wait_change_timer_set expired -> STATE_START\n"); + next_state = STATE_START; + wait_change_timer_set = 0; + } + else + { + /* We are here because the last replication session + * finished or aborted. + */ + wait_change_timer_set = 1; + protocol_sleep(prp, MAX_WAIT_BETWEEN_SESSIONS); + } + } + break; + case STATE_READY_TO_ACQUIRE: + + dev_debug("repl5_inc_run(STATE_READY_TO_ACQUIRE)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + + /* ONREPL - at this state we unconditionally acquire the replica + ignoring all events. Not sure if this is good */ + object_acquire(prp->replica_object); + replica = object_get_data(prp->replica_object); + + rc = acquire_replica(prp, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID, &ruv); + use_busy_backoff_timer = PR_FALSE; /* default */ + if (rc == ACQUIRE_SUCCESS) + { + next_state = STATE_SENDING_UPDATES; + } + else if (rc == ACQUIRE_REPLICA_BUSY) + { + next_state = STATE_BACKOFF_START; + use_busy_backoff_timer = PR_TRUE; + } + else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE) + { + next_state = STATE_WAIT_CHANGES; + } + else if (rc == ACQUIRE_TRANSIENT_ERROR) + { + next_state = STATE_BACKOFF_START; + } + else if (rc == ACQUIRE_FATAL_ERROR) + { + next_state = STATE_STOP_FATAL_ERROR; + } + if (rc != ACQUIRE_SUCCESS) + { + int optype, ldaprc; + conn_get_error(prp->conn, &optype, &ldaprc); + agmt_set_last_update_status(prp->agmt, ldaprc, + prp->last_acquire_response_code, NULL); + } + + object_release(prp->replica_object); replica = NULL; + break; + case STATE_BACKOFF_START: + dev_debug("repl5_inc_run(STATE_BACKOFF_START)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + next_state = STATE_START; + conn_set_agmt_changed(prp->conn); + } + else if (event_occurred (prp, EVENT_WINDOW_CLOSED)) + { + next_state = STATE_WAIT_WINDOW_OPEN; + } + else if (event_occurred (prp, EVENT_TRIGGERING_CRITERIA_MET)) + { + /* consume and ignore */ + } + else if (e1 = event_occurred (prp, EVENT_WINDOW_OPENED) || + event_occurred (prp, EVENT_BACKOFF_EXPIRED)) + { + /* This should never happen */ + /* this events - should not occur - log a warning and go to sleep */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental protocol: event %s should not occur in state %s\n", + agmt_get_long_name(prp->agmt), + e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED), + state2name(current_state)); + } + else + { + /* Set up the backoff timer to wake us up at the appropriate time */ + if (use_busy_backoff_timer) + { + /* we received a busy signal from the consumer, wait for a while */ + if (!busywaittime) + { + busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM; + } + prp_priv->backoff = backoff_new(BACKOFF_FIXED, busywaittime, + busywaittime); + } + else + { + prp_priv->backoff = backoff_new(BACKOFF_EXPONENTIAL, PROTOCOL_BACKOFF_MINIMUM, + PROTOCOL_BACKOFF_MAXIMUM); + } + next_state = STATE_BACKOFF; + backoff_reset(prp_priv->backoff, repl5_inc_backoff_expired, (void *)prp); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + use_busy_backoff_timer = PR_FALSE; + } + break; + case STATE_BACKOFF: + /* + * We're in a backoff state. + */ + dev_debug("repl5_inc_run(STATE_BACKOFF)"); + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + if (prp_priv->backoff) + backoff_delete(&prp_priv->backoff); + done = 1; + break; + } + else if (event_occurred(prp, EVENT_REPLICATE_NOW)) + { + next_state = STATE_READY_TO_ACQUIRE; + } + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + next_state = STATE_START; + + conn_set_agmt_changed(prp->conn); + /* Destroy the backoff timer, since we won't need it anymore */ + if (prp_priv->backoff) + backoff_delete(&prp_priv->backoff); + } + else if (event_occurred(prp, EVENT_WINDOW_CLOSED)) + { + next_state = STATE_WAIT_WINDOW_OPEN; + /* Destroy the backoff timer, since we won't need it anymore */ + if (prp_priv->backoff) + backoff_delete(&prp_priv->backoff); + } + else if (event_occurred(prp, EVENT_BACKOFF_EXPIRED)) + { + rc = acquire_replica(prp, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID, &ruv); + use_busy_backoff_timer = PR_FALSE; + if (rc == ACQUIRE_SUCCESS) + { + next_state = STATE_SENDING_UPDATES; + } + else if (rc == ACQUIRE_REPLICA_BUSY) + { + next_state = STATE_BACKOFF; + use_busy_backoff_timer = PR_TRUE; + } + else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE) + { + next_state = STATE_WAIT_CHANGES; + } + else if (rc == ACQUIRE_TRANSIENT_ERROR) + { + next_state = STATE_BACKOFF; + } + else if (rc == ACQUIRE_FATAL_ERROR) + { + next_state = STATE_STOP_FATAL_ERROR; + } + if (rc != ACQUIRE_SUCCESS) + { + int optype, ldaprc; + conn_get_error(prp->conn, &optype, &ldaprc); + agmt_set_last_update_status(prp->agmt, ldaprc, + prp->last_acquire_response_code, NULL); + } + /* + * We either need to step the backoff timer, or + * destroy it if we don't need it anymore. + */ + if (STATE_BACKOFF == next_state) + { + time_t next_fire_time; + time_t now; + /* Step the backoff timer */ + time(&now); + next_fire_time = backoff_step(prp_priv->backoff); + /* And go back to sleep */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Replication session backing off for %d seconds\n", + agmt_get_long_name(prp->agmt), + next_fire_time - now); + + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + else + { + /* Destroy the backoff timer, since we won't need it anymore */ + backoff_delete(&prp_priv->backoff); + } + use_busy_backoff_timer = PR_FALSE; + } + else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) + { + /* changes are available */ + if ( prp_priv->backoff == NULL || backoff_expired (prp_priv->backoff, 60) ) + { + /* + * Have seen cases that the agmt stuck here forever since + * somehow the backoff timer was not in event queue anymore. + * If the backoff timer has expired more than 60 seconds, + * destroy it. + */ + if ( prp_priv->backoff ) + backoff_delete(&prp_priv->backoff); + next_state = STATE_READY_TO_ACQUIRE; + } + else + { + /* ignore changes and go to sleep */ + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + } + else if (event_occurred(prp, EVENT_WINDOW_OPENED)) + { + /* this should never happen - log an error and go to sleep */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: Incremental protocol: " + "event %s should not occur in state %s; going to sleep\n", + agmt_get_long_name(prp->agmt), + event2name(EVENT_WINDOW_OPENED), state2name(current_state)); + protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT); + } + break; + case STATE_SENDING_UPDATES: + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES)"); + agmt_set_update_in_progress(prp->agmt, PR_TRUE); + num_changes_sent = 0; + last_start_time = current_time(); + agmt_set_last_update_start(prp->agmt, last_start_time); + /* + * We've acquired the replica, and are ready to send any + * needed updates. + */ + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + release_replica (prp); + done = 1; + agmt_set_update_in_progress(prp->agmt, PR_FALSE); + agmt_set_last_update_end(prp->agmt, current_time()); + /* MAB: I don't find the following status correct. How do we know it has + been stopped by an admin and not by a total update request, for instance? + In any case, how is this protocol shutdown situation different from all the + other ones that are present in this state machine? */ + /* richm: We at least need to let monitors know that the protocol has been + shutdown - maybe they can figure out why */ + agmt_set_last_update_status(prp->agmt, 0, 0, "Protocol stopped"); + break; + } + + agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update started"); + + /* ONREPL - in this state we send changes no matter what other events occur. + This is because we can get because of the REPLICATE_NOW event which + has high priority. Is this ok? */ + /* First, push new schema to the consumer if needed */ + /* ONREPL - should we push schema after we examine the RUV? */ + /* + * GGOOREPL - I don't see why we should wait until we've + * examined the RUV. The schema entry has its own CSN that is + * used to decide if the remote schema needs to be updated. + */ + cons_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt ); + rc = conn_push_schema(prp->conn, &cons_schema_csn); + if ( cons_schema_csn != agmt_get_consumer_schema_csn ( prp->agmt )) + { + agmt_set_consumer_schema_csn ( prp->agmt, cons_schema_csn ); + } + if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: unable to replicate schema: rc=%d\n", + agmt_get_long_name(prp->agmt), rc); + /* But keep going */ + } + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> examine_update_vector"); + rc = examine_update_vector(prp, ruv); + /* + * Decide what to do next - proceed with incremental, + * backoff, or total update + */ + switch (rc) + { + case EXAMINE_RUV_PARAM_ERROR: + /* this is really bad - we have NULL prp! */ + next_state = STATE_STOP_FATAL_ERROR; + break; + case EXAMINE_RUV_PRISTINE_REPLICA: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replica has no update vector. It has never been initialized.\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_BACKOFF_START; + break; + case EXAMINE_RUV_GENERATION_MISMATCH: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replica has a different generation ID than the local data.\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_BACKOFF_START; + break; + case EXAMINE_RUV_REPLICA_TOO_OLD: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Replica update vector is too out of date to bring " + "into sync using the incremental protocol. The replica " + "must be reinitialized.\n", agmt_get_long_name(prp->agmt)); + next_state = STATE_BACKOFF_START; + break; + case EXAMINE_RUV_OK: + /* update our csn generator state with the consumer's ruv data */ + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> examine_update_vector OK"); + object_acquire(prp->replica_object); + replica = object_get_data(prp->replica_object); + rc = replica_update_csngen_state (replica, ruv); + object_release (prp->replica_object); + replica = NULL; + if (rc != 0) /* too much skew */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental protocol: fatal error - too much time skew between replicas!\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_STOP_FATAL_ERROR; + } + else + { + rc = send_updates(prp, ruv, &num_changes_sent); + if (rc == UPDATE_NO_MORE_UPDATES) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_NO_MORE_UPDATES -> STATE_WAIT_CHANGES"); + agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded"); + next_state = STATE_WAIT_CHANGES; + } + else if (rc == UPDATE_YIELD) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_YIELD -> STATE_BACKOFF_START"); + agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded and yielded"); + next_state = STATE_BACKOFF_START; + } + else if (rc == UPDATE_TRANSIENT_ERROR) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TRANSIENT_ERROR -> STATE_BACKOFF_START"); + next_state = STATE_BACKOFF_START; + } + else if (rc == UPDATE_FATAL_ERROR) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_FATAL_ERROR -> STATE_STOP_FATAL_ERROR"); + next_state = STATE_STOP_FATAL_ERROR; + } + else if (rc == UPDATE_SCHEDULE_WINDOW_CLOSED) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_SCHEDULE_WINDOW_CLOSED -> STATE_WAIT_WINDOW_OPEN"); + /* ONREPL - I don't think we should check this. We might be + here because of replicate_now event - so we don't care + about the schedule */ + next_state = STATE_WAIT_WINDOW_OPEN; + /* ONREPL - do we need to release the replica here ? */ + conn_disconnect (prp->conn); + } + else if (rc == UPDATE_CONNECTION_LOST) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_CONNECTION_LOST -> STATE_BACKOFF_START"); + next_state = STATE_BACKOFF_START; + } + else if (rc == UPDATE_TIMEOUT) + { + dev_debug("repl5_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TIMEOUT -> STATE_BACKOFF_START"); + next_state = STATE_BACKOFF_START; + } + } + last_start_time = 0UL; + break; + } + if (NULL != ruv) + { + ruv_destroy(&ruv); ruv = NULL; + } + agmt_set_last_update_end(prp->agmt, current_time()); + agmt_set_update_in_progress(prp->agmt, PR_FALSE); + /* If timed out, close the connection after released the replica */ + release_replica(prp); + if (rc == UPDATE_TIMEOUT) { + conn_disconnect(prp->conn); + } + if (rc == UPDATE_NO_MORE_UPDATES && num_changes_sent > 0) + { + if (pausetime > 0) + { + /* richm - 20020219 - If we have acquired the consumer, and another master has gone + into backoff waiting for us to release it, we may acquire the replica sooner + than the other master has a chance to, and the other master may not be able + to acquire the consumer for a long time (hours, days?) if this server is + under a heavy load (see reliab06 et. al. system tests) + So, this sleep gives the other master(s) a chance to acquire the consumer + replica */ + long loops = pausetime; + /* the while loop is so that we don't just sleep and sleep if an + event comes in that we should handle immediately (like shutdown) */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Pausing updates for %ld seconds to allow other suppliers to update consumer\n", + agmt_get_long_name(prp->agmt), pausetime); + while (loops-- && !(PROTOCOL_IS_SHUTDOWN(prp))) + { + DS_Sleep(PR_SecondsToInterval(1)); + } + } + else if (num_changes_sent > 10) + { + /* wait for consumer to write its ruv if the replication was busy */ + /* When asked, consumer sends its ruv in cache to the supplier. */ + /* DS_Sleep ( PR_SecondsToInterval(1) ); */ + } + } + break; + case STATE_STOP_FATAL_ERROR: + /* + * We encountered some sort of a fatal error. Suspend. + */ + /* XXXggood update state in replica */ + agmt_set_last_update_status(prp->agmt, -1, 0, "Incremental update has failed and requires administrator action"); + dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR)"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incremental update failed and requires administrator action\n", + agmt_get_long_name(prp->agmt)); + next_state = STATE_STOP_FATAL_ERROR_PART2; + break; + case STATE_STOP_FATAL_ERROR_PART2: + if (PROTOCOL_IS_SHUTDOWN(prp)) + { + done = 1; + break; + } + + /* MAB: This state is the FATAL state where we are supposed to get + as a result of a FATAL error on send_updates. But, as bug + states, send_updates was always returning TRANSIENT errors and never + FATAL... In other words, this code has never been tested before... + + As of 01/16/01, this piece of code was in a very dangerous state. In particular, + 1) it does not catch any events + 2) it is a terminal state (once reached it never transitions to a different state) + + Both things combined make this state to become a consuming infinite loop + that is useless after all (we are in a fatal place requiring manual admin jobs */ + + /* MAB: The following lines fix problem number 1 above... When the code gets + into this state, it should only get a chance to get out of it by an + EVENT_AGMT_CHANGED event... All other events should be ignored */ + else if (event_occurred(prp, EVENT_AGMT_CHANGED)) + { + dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR): EVENT_AGMT_CHANGED received\n"); + /* Chance to recover for the EVENT_AGMT_CHANGED event. + This is not mandatory, but fixes problem 2 above */ + next_state = STATE_STOP_NORMAL_TERMINATION; + } + else + { + dev_debug("repl5_inc_run(STATE_STOP_FATAL_ERROR): Event received. Clearing it\n"); + reset_events (prp); + } + + protocol_sleep (prp, PR_INTERVAL_NO_TIMEOUT); + break; + + case STATE_STOP_NORMAL_TERMINATION: + /* + * We encountered some sort of a fatal error. Return. + */ + /* XXXggood update state in replica */ + dev_debug("repl5_inc_run(STATE_STOP_NORMAL_TERMINATION)"); + done = 1; + break; + } + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: State: %s -> %s\n", + agmt_get_long_name(prp->agmt), + state2name(current_state), state2name(next_state)); + + current_state = next_state; + } while (!done); + slapi_ch_free((void**)&hostname); + /* remove_protocol_callbacks(prp); */ + prp->stopped = 1; + /* Cancel any linger timer that might be in effect... */ + conn_cancel_linger(prp->conn); + /* ... and disconnect, if currently connected */ + conn_disconnect(prp->conn); +} + + + +/* + * Go to sleep until awakened. + */ +static void +protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration) +{ + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + /* we should not go to sleep if there are events available to be processed. + Otherwise, we can miss the event that suppose to wake us up */ + if (prp->eventbits == 0) + PR_WaitCondVar(prp->cvar, duration); + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Incremental protocol: can't go to sleep: event bits - %x\n", + agmt_get_long_name(prp->agmt), prp->eventbits); + } + PR_Unlock(prp->lock); +} + + +/* + * Notify the protocol about some event. Signal the condition + * variable in case the protocol is sleeping. Multiple occurences + * of a single event type are not remembered (e.g. no stack + * of events is maintained). + */ +static void +event_notify(Private_Repl_Protocol *prp, PRUint32 event) +{ + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + prp->eventbits |= event; + PR_NotifyCondVar(prp->cvar); + PR_Unlock(prp->lock); +} + + +/* + * Test to see if an event occurred. The event is cleared when + * read. + */ +static PRUint32 +event_occurred(Private_Repl_Protocol *prp, PRUint32 event) +{ + PRUint32 return_value; + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + return_value = (prp->eventbits & event); + prp->eventbits &= ~event; /* Clear event */ + PR_Unlock(prp->lock); + return return_value; +} + +static void +reset_events (Private_Repl_Protocol *prp) +{ + PR_ASSERT(NULL != prp); + PR_Lock(prp->lock); + prp->eventbits = 0; + PR_Unlock(prp->lock); +} + + +/* + * Replay the actual update to the consumer. Construct an appropriate LDAP + * operation, attach the baggage LDAPv3 control that contains the CSN, etc., + * and send the operation to the consumer. + */ +ConnResult +replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op) +{ + ConnResult return_value; + LDAPControl *update_control; + char *parentuniqueid; + LDAPMod **modrdn_mods = NULL; + char csn_str[CSN_STRSIZE]; /* For logging only */ + + csn_as_string(op->csn, PR_FALSE, csn_str); + + /* Construct the replication info control that accompanies the operation */ + if (SLAPI_OPERATION_ADD == op->operation_type) + { + parentuniqueid = op->p.p_add.parentuniqueid; + } + else if (SLAPI_OPERATION_MODRDN == op->operation_type) + { + /* + * For modrdn operations, we need to send along modified attributes, e.g. + * modifytimestamp. + * And the superior_uniqueid ! + */ + modrdn_mods = op->p.p_modrdn.modrdn_mods; + parentuniqueid = op->p.p_modrdn.modrdn_newsuperior_address.uniqueid; + } + else + { + parentuniqueid = NULL; + } + if (create_NSDS50ReplUpdateInfoControl(op->target_address.uniqueid, + parentuniqueid, op->csn, modrdn_mods, &update_control) != LDAP_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: replay_update: Unable to create NSDS50ReplUpdateInfoControl " + "for operation with csn %s. Skipping update.\n", + agmt_get_long_name(prp->agmt), csn_str); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: replay_update: Sending %s operation (dn=\"%s\" csn=%s)\n", + agmt_get_long_name(prp->agmt), + op2string(op->operation_type), op->target_address.dn, csn_str); + /* What type of operation is it? */ + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: + { + LDAPMod **entryattrs; + /* Convert entry to mods */ + (void)slapi_entry2mods (op->p.p_add.target_entry, + NULL /* &entrydn : We don't need it */, + &entryattrs); + if (NULL == entryattrs) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: replay_update: Cannot convert entry to LDAPMods.\n", + agmt_get_long_name(prp->agmt)); + return_value = CONN_LOCAL_ERROR; + } + else + { + return_value = conn_send_add(prp->conn, op->target_address.dn, + entryattrs, update_control, NULL /* returned controls */); + ldap_mods_free(entryattrs, 1); + } + break; + } + case SLAPI_OPERATION_MODIFY: + return_value = conn_send_modify(prp->conn, op->target_address.dn, + op->p.p_modify.modify_mods, update_control, + NULL /* returned controls */); + break; + case SLAPI_OPERATION_DELETE: + return_value = conn_send_delete(prp->conn, op->target_address.dn, + update_control, NULL /* returned controls */); + break; + case SLAPI_OPERATION_MODRDN: + /* XXXggood need to pass modrdn mods in update control! */ + return_value = conn_send_rename(prp->conn, op->target_address.dn, + op->p.p_modrdn.modrdn_newrdn, + op->p.p_modrdn.modrdn_newsuperior_address.dn, + op->p.p_modrdn.modrdn_deloldrdn, + update_control, NULL /* returned controls */); + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: replay_update: Unknown " + "operation type %d found in changelog - skipping change.\n", + agmt_get_long_name(prp->agmt), op->operation_type); + } + + destroy_NSDS50ReplUpdateInfoControl(&update_control); + } + + if (CONN_OPERATION_SUCCESS == return_value) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: replay_update: Consumer successfully replayed operation with csn %s\n", + agmt_get_long_name(prp->agmt), csn_str); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: replay_update: Consumer could not replay operation with csn %s\n", + agmt_get_long_name(prp->agmt), csn_str); + } + return return_value; +} + +static PRBool +is_dummy_operation (const slapi_operation_parameters *op) +{ + return (strcmp (op->target_address.uniqueid, START_ITERATION_ENTRY_UNIQUEID) == 0); +} + + + +void +cl5_operation_parameters_done (struct slapi_operation_parameters *sop) +{ + if(sop!=NULL) { + switch(sop->operation_type) + { + case SLAPI_OPERATION_BIND: + slapi_ch_free((void **)&(sop->p.p_bind.bind_saslmechanism)); + if (sop->p.p_bind.bind_creds) + ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_creds)); + if (sop->p.p_bind.bind_ret_saslcreds) + ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_ret_saslcreds)); + sop->p.p_bind.bind_creds = NULL; + sop->p.p_bind.bind_ret_saslcreds = NULL; + break; + case SLAPI_OPERATION_COMPARE: + ava_done((struct ava *)&(sop->p.p_compare.compare_ava)); + break; + case SLAPI_OPERATION_SEARCH: + slapi_ch_free((void **)&(sop->p.p_search.search_strfilter)); + charray_free(sop->p.p_search.search_attrs); + slapi_filter_free(sop->p.p_search.search_filter,1); + break; + case SLAPI_OPERATION_MODRDN: + sop->p.p_modrdn.modrdn_deloldrdn = 0; + break; + case SLAPI_OPERATION_EXTENDED: + slapi_ch_free((void **)&(sop->p.p_extended.exop_oid)); + if (sop->p.p_extended.exop_value) + ber_bvecfree((struct berval**)&(sop->p.p_extended.exop_value)); + sop->p.p_extended.exop_value = NULL; + break; + default: + break; + } + } + operation_parameters_done(sop); + +} + + + +/* + * Send a set of updates to the replica. Assumes that (1) the replica + * has already been acquired, (2) that the consumer's update vector has + * been checked and (3) that it's ok to send incremental updates. + * Returns: + * UPDATE_NO_MORE_UPDATES - all updates were sent succussfully + * UPDATE_TRANSIENT_ERROR - some non-permanent error occurred. Try again later. + * UPDATE_FATAL_ERROR - some bad, permanent error occurred. + * UPDATE_SCHEDULE_WINDOW_CLOSED - the schedule window closed on us. + */ +static int +send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *num_changes_sent) +{ + CL5Entry entry; + slapi_operation_parameters op; + int return_value; + int rc; + CL5ReplayIterator *changelog_iterator; + + *num_changes_sent = 0; + /* + * Iterate over the changelog. Retrieve each update, + * construct an appropriate LDAP operation, + * attaching the CSN, and send the change. + */ + + rc = cl5CreateReplayIterator(prp, remote_update_vector, &changelog_iterator); + if (CL5_SUCCESS != rc) + { + switch (rc) + { + case CL5_BAD_DATA: /* invalid parameter passed to the function */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Invalid parameter passed to cl5CreateReplayIterator\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_BAD_FORMAT: /* db data has unexpected format */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unexpected format encountered in changelog database\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_BAD_STATE: /* changelog is in an incorrect state for attempted operation */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Changelog database was in an incorrect state\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_BAD_DBVERSION: /* changelog has invalid dbversion */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Incorrect dbversion found in changelog database\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_DB_ERROR: /* database error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A changelog database error was encountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_NOTFOUND: /* we have no changes to send */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No changes to send\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_NO_MORE_UPDATES; + break; + case CL5_MEMORY_ERROR: /* memory allocation failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Memory allocation error occurred\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_SYSTEM_ERROR: /* NSPR error occurred: use PR_GetError for furhter info */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An NSPR error (%d) occurred\n", + agmt_get_long_name(prp->agmt), PR_GetError()); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_CSN_ERROR: /* CSN API failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A CSN API failure was encountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_RUV_ERROR: /* RUV API failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An RUV API failure occurred\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_OBJSET_ERROR: /* namedobjset api failed */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A namedobject API failure occurred\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + case CL5_PURGED_DATA: /* requested data has been purged */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Data required to update replica has been purged. " + "The replica must be reinitialized.\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_MISSING_DATA: /* data should be in the changelog, but is missing */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Missing data encountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + case CL5_UNKNOWN_ERROR: /* unclassified error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An unknown error was ecountered\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_TRANSIENT_ERROR; + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: An unknown error (%d) occurred " + "(cl5CreateReplayIterator)\n", + agmt_get_long_name(prp->agmt), rc); + return_value = UPDATE_TRANSIENT_ERROR; + } + } + else + { + int finished = 0; + ConnResult replay_crc; + char csn_str[CSN_STRSIZE]; + + memset ( (void*)&op, 0, sizeof (op) ); + entry.op = &op; + do { + cl5_operation_parameters_done ( entry.op ); + memset ( (void*)entry.op, 0, sizeof (op) ); + rc = cl5GetNextOperationToReplay(changelog_iterator, &entry); + switch (rc) + { + case CL5_SUCCESS: + /* check that we don't return dummy entries */ + if (is_dummy_operation (entry.op)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: changelog iteration code returned a dummy entry with csn %s, " + "skipping ...\n", + agmt_get_long_name(prp->agmt), csn_as_string(entry.op->csn, PR_FALSE, csn_str)); + continue; + } + replay_crc = replay_update(prp, entry.op); + if (CONN_OPERATION_SUCCESS != replay_crc) + { + int operation, error; + conn_get_error(prp->conn, &operation, &error); + csn_as_string(entry.op->csn, PR_FALSE, csn_str); + /* Figure out what to do next */ + if (CONN_OPERATION_FAILED == replay_crc) + { + /* Map ldap error code to return value */ + if (!ignore_error_and_keep_going(error)) + { + return_value = UPDATE_TRANSIENT_ERROR; + finished = 1; + } + else + { + agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 1 /*skipped*/); + } + slapi_log_error(finished ? SLAPI_LOG_FATAL : slapi_log_urp, repl_plugin_name, + "%s: Consumer failed to replay change (uniqueid %s, CSN %s): %s. %s.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str, + ldap_err2string(error), + finished ? "Will retry later" : "Skipping"); + } + else if (CONN_NOT_CONNECTED == replay_crc) + { + /* We lost the connection - enter backoff state */ + + return_value = UPDATE_TRANSIENT_ERROR; + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Consumer failed to replay change (uniqueid %s, CSN %s): " + "%s. Will retry later.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str, + error ? ldap_err2string(error) : "Connection lost"); + } + else if (CONN_TIMEOUT == replay_crc) + { + return_value = UPDATE_TIMEOUT; + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Consumer timed out to replay change (uniqueid %s, CSN %s): " + "%s.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str, + error ? ldap_err2string(error) : "Timeout"); + } + else if (CONN_LOCAL_ERROR == replay_crc) + { + /* + * Something bad happened on the local server - enter + * backoff state. + */ + return_value = UPDATE_TRANSIENT_ERROR; + finished = 1; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Failed to replay change (uniqueid %s, CSN %s): " + "Local error. Will retry later.\n", + agmt_get_long_name(prp->agmt), + entry.op->target_address.uniqueid, csn_str); + } + + } + else + { + /* Positive response received */ + (*num_changes_sent)++; + agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 0 /*replayed*/); + } + break; + case CL5_BAD_DATA: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Invalid parameter passed to cl5GetNextOperationToReplay\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + finished = 1; + break; + case CL5_NOTFOUND: + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: No more updates to send (cl5GetNextOperationToReplay)\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_NO_MORE_UPDATES; + finished = 1; + break; + case CL5_DB_ERROR: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A database error occurred (cl5GetNextOperationToReplay)\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + finished = 1; + break; + case CL5_BAD_FORMAT: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A malformed changelog entry was encountered (cl5GetNextOperationToReplay)\n", + agmt_get_long_name(prp->agmt)); + break; + case CL5_MEMORY_ERROR: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: A memory allocation error occurred (cl5GetNextOperationToRepla)\n", + agmt_get_long_name(prp->agmt)); + return_value = UPDATE_FATAL_ERROR; + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unknown error code (%d) returned from cl5GetNextOperationToReplay\n", + agmt_get_long_name(prp->agmt), rc); + return_value = UPDATE_TRANSIENT_ERROR; + break; + } + /* Check for protocol shutdown */ + if (prp->terminate) + { + return_value = UPDATE_NO_MORE_UPDATES; + finished = 1; + } + if (*num_changes_sent >= MAX_CHANGES_PER_SESSION) + { + return_value = UPDATE_YIELD; + finished = 1; + } + } while (!finished); + cl5_operation_parameters_done ( entry.op ); + cl5DestroyReplayIterator(&changelog_iterator); + } + return return_value; +} + + + +/* + * XXXggood this should probably be in the superclass, since the full update + * protocol is going to need it too. + */ +static int +repl5_inc_stop(Private_Repl_Protocol *prp) +{ + int return_value; + PRIntervalTime start, maxwait, now; + int seconds = 1200; + + maxwait = PR_SecondsToInterval(seconds); + prp->terminate = 1; + event_notify(prp, EVENT_PROTOCOL_SHUTDOWN); + start = PR_IntervalNow(); + now = start; + while (!prp->stopped && ((now - start) < maxwait)) + { + DS_Sleep(PR_SecondsToInterval(1)); + now = PR_IntervalNow(); + } + if (!prp->stopped) + { + /* Isn't listening. Do something drastic. */ + return_value = -1; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: repl5_inc_stop: protocol does not stop after %d seconds\n", + agmt_get_long_name(prp->agmt), seconds); + } + else + { + return_value = 0; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: repl5_inc_stop: protocol stopped after %d seconds\n", + agmt_get_long_name(prp->agmt), + PR_IntervalToSeconds(now-start)); + } + return return_value; +} + + + +static int +repl5_inc_status(Private_Repl_Protocol *prp) +{ + int return_value = 0; + + return return_value; +} + + + +static void +repl5_inc_notify_update(Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_TRIGGERING_CRITERIA_MET); +} + + +static void +repl5_inc_update_now(Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_REPLICATE_NOW); +} + + +static void +repl5_inc_notify_agmt_changed(Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_AGMT_CHANGED); +} + +static void +repl5_inc_notify_window_opened (Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_WINDOW_OPENED); +} + +static void +repl5_inc_notify_window_closed (Private_Repl_Protocol *prp) +{ + event_notify(prp, EVENT_WINDOW_CLOSED); +} + +Private_Repl_Protocol * +Repl_5_Inc_Protocol_new(Repl_Protocol *rp) +{ + repl5_inc_private *rip = NULL; + Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol)); + prp->delete = repl5_inc_delete; + prp->run = repl5_inc_run; + prp->stop = repl5_inc_stop; + prp->status = repl5_inc_status; + prp->notify_update = repl5_inc_notify_update; + prp->notify_agmt_changed = repl5_inc_notify_agmt_changed; + prp->notify_window_opened = repl5_inc_notify_window_opened; + prp->notify_window_closed = repl5_inc_notify_window_closed; + prp->update_now = repl5_inc_update_now; + prp->replica_object = prot_get_replica_object(rp); + if ((prp->lock = PR_NewLock()) == NULL) + { + goto loser; + } + if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL) + { + goto loser; + } + prp->stopped = 0; + prp->terminate = 0; + prp->eventbits = 0; + prp->conn = prot_get_connection(rp); + prp->agmt = prot_get_agreement(rp); + prp->last_acquire_response_code = NSDS50_REPL_REPLICA_READY; + rip = (void *)slapi_ch_malloc(sizeof(repl5_inc_private)); + rip->ruv = NULL; + rip->backoff = NULL; + rip->rp = rp; + prp->private = (void *)rip; + prp->replica_acquired = PR_FALSE; + return prp; +loser: + repl5_inc_delete(&prp); + return NULL; +} + + + + +static void +repl5_inc_backoff_expired(time_t timer_fire_time, void *arg) +{ + Private_Repl_Protocol *prp = (Private_Repl_Protocol *)arg; + PR_ASSERT(NULL != prp); + event_notify(prp, EVENT_BACKOFF_EXPIRED); +} + + + +/* + * Examine the update vector and determine our course of action. + * There are 3 different possibilities, plus a catch-all error: + * 1 - no update vector (ruv is NULL). The consumer's replica is + * pristine, so it needs to be initialized. Return + * EXAMINE_RUV_PRISTINE_REPLICA. + * 2 - ruv is present, but its database generation ID doesn't + * match the local generation ID. This means that either + * the local replica must be reinitialized from the remote + * replica or vice-versa. Return + * EXAMINE_RUV_GENERATION_MISMATCH. + * 3 - ruv is present, and we have all updates needed to bring + * the replica up to date using the incremental protocol. + * return EXAMINE_RUV_OK. + * 4 - parameter error. Return EXAMINE_RUV_PARAM_ERROR + */ +static int +examine_update_vector(Private_Repl_Protocol *prp, RUV *remote_ruv) +{ + int return_value; + + PR_ASSERT(NULL != prp); + if (NULL == prp) + { + return_value = EXAMINE_RUV_PARAM_ERROR; + } + else if (NULL == remote_ruv) + { + return_value = EXAMINE_RUV_PRISTINE_REPLICA; + } + else + { + char *local_gen = NULL; + char *remote_gen = ruv_get_replica_generation(remote_ruv); + Object *local_ruv_obj; + RUV *local_ruv; + Replica *replica; + + PR_ASSERT(NULL != prp->replica_object); + replica = object_get_data(prp->replica_object); + PR_ASSERT(NULL != replica); + local_ruv_obj = replica_get_ruv (replica); + if (NULL != local_ruv_obj) + { + local_ruv = (RUV*)object_get_data (local_ruv_obj); + PR_ASSERT (local_ruv); + local_gen = ruv_get_replica_generation(local_ruv); + object_release (local_ruv_obj); + } + if (NULL == remote_gen || NULL == local_gen || strcmp(remote_gen, local_gen) != 0) + { + return_value = EXAMINE_RUV_GENERATION_MISMATCH; + } + else + { + return_value = EXAMINE_RUV_OK; + } + slapi_ch_free((void**)&remote_gen); + slapi_ch_free((void**)&local_gen); + } + return return_value; +} + + +/* + * When we get an error from an LDAP operation, we call this + * function to decide if we should just keep replaying + * updates, or if we should stop, back off, and try again + * later. + * Returns PR_TRUE if we shoould keep going, PR_FALSE if + * we should back off and try again later. + * + * In general, we keep going if the return code is consistent + * with some sort of bug in URP that causes the consumer to + * emit an error code that it shouldn't have, e.g. LDAP_ALREADY_EXISTS. + * + * We stop if there's some indication that the server just completely + * failed to process the operation, e.g. LDAP_OPERATIONS_ERROR. + */ +static PRBool +ignore_error_and_keep_going(int error) +{ + int return_value; + + switch (error) + { + /* Cases where we keep going */ + case LDAP_SUCCESS: + case LDAP_NO_SUCH_ATTRIBUTE: + case LDAP_UNDEFINED_TYPE: + case LDAP_CONSTRAINT_VIOLATION: + case LDAP_TYPE_OR_VALUE_EXISTS: + case LDAP_INVALID_SYNTAX: + case LDAP_NO_SUCH_OBJECT: + case LDAP_INVALID_DN_SYNTAX: + case LDAP_IS_LEAF: + case LDAP_INSUFFICIENT_ACCESS: + case LDAP_NAMING_VIOLATION: + case LDAP_OBJECT_CLASS_VIOLATION: + case LDAP_NOT_ALLOWED_ON_NONLEAF: + case LDAP_NOT_ALLOWED_ON_RDN: + case LDAP_ALREADY_EXISTS: + case LDAP_NO_OBJECT_CLASS_MODS: + return_value = PR_TRUE; + break; + + /* Cases where we stop and retry */ + case LDAP_OPERATIONS_ERROR: + case LDAP_PROTOCOL_ERROR: + case LDAP_TIMELIMIT_EXCEEDED: + case LDAP_SIZELIMIT_EXCEEDED: + case LDAP_STRONG_AUTH_NOT_SUPPORTED: + case LDAP_STRONG_AUTH_REQUIRED: + case LDAP_PARTIAL_RESULTS: + case LDAP_REFERRAL: + case LDAP_ADMINLIMIT_EXCEEDED: + case LDAP_UNAVAILABLE_CRITICAL_EXTENSION: + case LDAP_CONFIDENTIALITY_REQUIRED: + case LDAP_SASL_BIND_IN_PROGRESS: + case LDAP_INAPPROPRIATE_MATCHING: + case LDAP_ALIAS_PROBLEM: + case LDAP_ALIAS_DEREF_PROBLEM: + case LDAP_INAPPROPRIATE_AUTH: + case LDAP_INVALID_CREDENTIALS: + case LDAP_BUSY: + case LDAP_UNAVAILABLE: + case LDAP_UNWILLING_TO_PERFORM: + case LDAP_LOOP_DETECT: + case LDAP_SORT_CONTROL_MISSING: + case LDAP_INDEX_RANGE_ERROR: + case LDAP_RESULTS_TOO_LARGE: + case LDAP_AFFECTS_MULTIPLE_DSAS: + case LDAP_OTHER: + case LDAP_SERVER_DOWN: + case LDAP_LOCAL_ERROR: + case LDAP_ENCODING_ERROR: + case LDAP_DECODING_ERROR: + case LDAP_TIMEOUT: + case LDAP_AUTH_UNKNOWN: + case LDAP_FILTER_ERROR: + case LDAP_USER_CANCELLED: + case LDAP_PARAM_ERROR: + case LDAP_NO_MEMORY: + case LDAP_CONNECT_ERROR: + case LDAP_NOT_SUPPORTED: + case LDAP_CONTROL_NOT_FOUND: + case LDAP_NO_RESULTS_RETURNED: + case LDAP_MORE_RESULTS_TO_RETURN: + case LDAP_CLIENT_LOOP: + case LDAP_REFERRAL_LIMIT_EXCEEDED: + return_value = PR_FALSE; + break; + } + return return_value; +} + +/* this function converts a state to its name - for debug output */ +static const char* +state2name (int state) +{ + switch (state) + { + case STATE_START: return "start"; + case STATE_WAIT_WINDOW_OPEN: return "wait_for_window_to_open"; + case STATE_WAIT_CHANGES: return "wait_for_changes"; + case STATE_READY_TO_ACQUIRE: return "ready_to_acquire_replica"; + case STATE_BACKOFF_START: return "start_backoff"; + case STATE_BACKOFF: return "backoff"; + case STATE_SENDING_UPDATES: return "sending_updates"; + case STATE_STOP_FATAL_ERROR: return "stop_fatal_error"; + case STATE_STOP_FATAL_ERROR_PART2: return "stop_fatal_error"; + case STATE_STOP_NORMAL_TERMINATION: return "stop_normal_termination"; + default: return "invalid_state"; + } +} + +/* this function convert s an event to its name - for debug output */ +static const char* +event2name (int event) +{ + switch (event) + { + case EVENT_WINDOW_OPENED: return "update_window_opened"; + case EVENT_WINDOW_CLOSED: return "update_window_closed"; + case EVENT_TRIGGERING_CRITERIA_MET: return "data_modified"; + case EVENT_BACKOFF_EXPIRED: return "backoff_timer_expired"; + case EVENT_REPLICATE_NOW: return "replicate_now"; + case EVENT_PROTOCOL_SHUTDOWN: return "protocol_shutdown"; + case EVENT_AGMT_CHANGED: return "agreement_changed"; + default: return "invalid_event"; + } +} + +static const char* +op2string(int op) +{ + switch (op) { + case SLAPI_OPERATION_ADD: + return "add"; + case SLAPI_OPERATION_MODIFY: + return "modify"; + case SLAPI_OPERATION_DELETE: + return "delete"; + case SLAPI_OPERATION_MODRDN: + return "rename"; + case SLAPI_OPERATION_EXTENDED: + return "extended"; + } + + return "unknown"; +} diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c new file mode 100644 index 00000000..eae3b238 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_init.c @@ -0,0 +1,572 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + repl5_init.c - plugin initialization functions +*/ + +/* + * Add an entry like the following to dse.ldif to enable this plugin: + +dn: cn=Multi-Master Replication Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: Legacy Replication Plugin +nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/replication-plugin.so +nsslapd-plugininitfunc: replication_multimaster_plugin_init +nsslapd-plugintype: object +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database +nsslapd-plugin-depends-on-named: Class of Service +nsslapd-pluginid: replication-multimaster +nsslapd-pluginversion: 5.0b1 +nsslapd-pluginvendor: Netscape Communications +nsslapd-plugindescription: Multi-Master Replication Plugin + +*/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "cl5.h" /* changelog interface */ +#include "dirver.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +/* #ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif*/ + +#define NSDS_REPL_NAME_PREFIX "Netscape Replication" + +static char *start_oid_list[] = { + REPL_START_NSDS50_REPLICATION_REQUEST_OID, + NULL +}; +static char *start_name_list[] = { + NSDS_REPL_NAME_PREFIX " Start Session", + NULL +}; +static char *end_oid_list[] = { + REPL_END_NSDS50_REPLICATION_REQUEST_OID, + NULL +}; +static char *end_name_list[] = { + NSDS_REPL_NAME_PREFIX " End Session", + NULL +}; +static char *total_oid_list[] = { + REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID, + NULL +}; +static char *total_name_list[] = { + NSDS_REPL_NAME_PREFIX " Total Update Entry", + NULL +}; +static char *response_oid_list[] = { + REPL_NSDS50_REPLICATION_RESPONSE_OID, + NULL +}; +static char *response_name_list[] = { + NSDS_REPL_NAME_PREFIX " Response", + NULL +}; + +/* List of plugin identities for every plugin registered. Plugin identity + is passed by the server in the plugin init function and must be supplied + by the plugin to all internal operations it initiates + */ + +/* ----------------------------- Multi-Master Replication Plugin */ + +static Slapi_PluginDesc multimasterdesc = {"replication-multimaster", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master Replication Plugin"}; +static Slapi_PluginDesc multimasterpreopdesc = {"replication-multimaster-preop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication pre-operation plugin"}; +static Slapi_PluginDesc multimasterpostopdesc = {"replication-multimaster-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication post-operation plugin"}; +static Slapi_PluginDesc multimasterinternalpreopdesc = {"replication-multimaster-internalpreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multi-master replication internal pre-operation plugin"}; +static Slapi_PluginDesc multimasterinternalpostopdesc = {"replication-multimaster-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication internal post-operation plugin"}; +static Slapi_PluginDesc multimasterbepreopdesc = {"replication-multimaster-bepreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication bepre-operation plugin"}; +static Slapi_PluginDesc multimasterbepostopdesc = {"replication-multimaster-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication bepost-operation plugin"}; +static Slapi_PluginDesc multimasterextopdesc = { "replication-multimaster-extop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Multimaster replication extended-operation plugin" }; + +static int multimaster_stopped_flag; /* A flag which is set when all the plugin threads are to stop */ +static int multimaster_started_flag = 0; + +/* Thread private data and interface */ +static PRUintn thread_private_agmtname; /* thread private index for logging*/ +static PRUintn thread_private_cache; + +char* +get_thread_private_agmtname() +{ + char *agmtname = NULL; + if (thread_private_agmtname) + agmtname = PR_GetThreadPrivate(thread_private_agmtname); + return (agmtname ? agmtname : ""); +} + +void +set_thread_private_agmtname(const char *agmtname) +{ + if (thread_private_agmtname) + PR_SetThreadPrivate(thread_private_agmtname, (void *)agmtname); +} + +void* +get_thread_private_cache () +{ + void *buf = NULL; + if ( thread_private_cache ) + buf = PR_GetThreadPrivate ( thread_private_cache ); + return buf; +} + +void +set_thread_private_cache ( void *buf ) +{ + if ( thread_private_cache ) + PR_SetThreadPrivate ( thread_private_cache, buf ); +} + +char* +get_repl_session_id (Slapi_PBlock *pb, char *idstr, CSN **csn) +{ + int connid=-1, opid=-1; + CSN *opcsn; + char opcsnstr[CSN_STRSIZE]; + + *idstr = '\0'; + opcsn = NULL; + opcsnstr[0] = '\0'; + + if (pb) { + Slapi_Operation *op; + slapi_pblock_get (pb, SLAPI_OPERATION_ID, &opid); + /* Avoid "Connection is NULL and hence cannot access SLAPI_CONN_ID" */ + if (opid) { + slapi_pblock_get (pb, SLAPI_CONN_ID, &connid); + sprintf (idstr, "conn=%d op=%d", connid, opid); + } + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + opcsn = operation_get_csn (op); + if (opcsn) { + csn_as_string (opcsn, PR_FALSE, opcsnstr); + strcat (idstr, " csn="); + strcat (idstr, opcsnstr); + } + } + if (csn) { + *csn = opcsn; + } + return idstr; +} + + +int +multimaster_preop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterpreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) multimaster_preop_bind ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) multimaster_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void *) multimaster_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) multimaster_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void *) multimaster_preop_modrdn ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) multimaster_preop_search ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void *) multimaster_preop_compare ) != 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_preop_init failed\n" ); + rc= -1; + } + return rc; +} + +int +multimaster_postop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_BIND_FN, (void *) multimaster_postop_bind ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) multimaster_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) multimaster_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) multimaster_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) multimaster_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_postop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_internalpreop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterinternalpreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) multimaster_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, (void *) multimaster_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) multimaster_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, (void *) multimaster_preop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_internalpreop_init failed\n" ); + rc= -1; + } + return rc; +} + +int +multimaster_internalpostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterinternalpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) multimaster_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) multimaster_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) multimaster_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) multimaster_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_internalpostop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_bepreop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbepreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_ADD_FN, (void *) multimaster_bepreop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_DELETE_FN, (void *) multimaster_bepreop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_MODIFY_FN, (void *) multimaster_bepreop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_PRE_MODRDN_FN, (void *) multimaster_bepreop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_bepreop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_bepostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbepostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_POST_MODRDN_FN, (void *) multimaster_bepostop_modrdn ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_BE_POST_DELETE_FN, (void *) multimaster_bepostop_delete ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_bepostop_init failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_start_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)start_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)start_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_StartNSDS50ReplicationRequest )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (StartNSDS50ReplicationRequest) failed\n" ); + rc= -1; + } + + + return rc; +} + + +int +multimaster_end_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)end_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)end_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_EndNSDS50ReplicationRequest )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_end_extop_init (EndNSDS50ReplicationRequest) failed\n" ); + rc= -1; + } + + return rc; +} + + +int +multimaster_total_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + void *identity = NULL; + + /* get plugin identity and store it to pass to internal operations */ + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)total_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)total_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)multimaster_extop_NSDS50ReplicationEntry )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (NSDS50ReplicationEntry failed\n" ); + rc= -1; + } + + return rc; +} + +int +multimaster_response_extop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + void *identity = NULL; + + /* get plugin identity and store it to pass to internal operations */ + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterextopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, (void *)response_oid_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, (void *)response_name_list ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)extop_noop )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_start_extop_init (NSDS50ReplicationResponse failed\n" ); + rc= -1; + } + + return rc; +} + + +static PRBool +check_for_ldif_dump(Slapi_PBlock *pb) +{ + int i; + int argc; + char **argv; + PRBool return_value = PR_FALSE; + + slapi_pblock_get( pb, SLAPI_ARGC, &argc); + slapi_pblock_get( pb, SLAPI_ARGV, &argv); + + for (i = 1; i < argc && !return_value; i++) + { + if (strcmp(argv[i], "db2ldif") == 0) + { + return_value = PR_TRUE; + } + } + return return_value; +} + + +static PRBool is_ldif_dump = PR_FALSE; + +int +multimaster_start( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if (!multimaster_started_flag) + { + /* Initialize thread private data for logging. Ignore if fails */ + PR_NewThreadPrivateIndex (&thread_private_agmtname, NULL); + PR_NewThreadPrivateIndex (&thread_private_cache, NULL); + + /* Decode the command line args to see if we're dumping to LDIF */ + is_ldif_dump = check_for_ldif_dump(pb); + + /* allow online replica configuration */ + rc = replica_config_init (); + if (rc != 0) + goto out; + + slapi_register_supported_control(REPL_NSDS50_UPDATE_INFO_CONTROL_OID, + SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE | + SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN); + + /* Stash away our partial URL, used in RUVs */ + rc = multimaster_set_local_purl(); + if (rc != 0) + goto out; + + /* Initialise support for cn=monitor */ + rc = repl_monitor_init(); + if (rc != 0) + goto out; + + /* initialize name hash */ + rc = replica_init_name_hash (); + if (rc != 0) + goto out; + + /* initialize dn hash */ + rc = replica_init_dn_hash (); + if (rc != 0) + goto out; + + /* create replicas */ + multimaster_mtnode_construct_replicas (); + + /* Initialise the 5.0 Changelog */ + rc = changelog5_init(); + if (rc != 0) + goto out; + + /* Initialize the replication agreements, unless we're dumping LDIF */ + if (!is_ldif_dump) + { + rc = agmtlist_config_init(); + if (rc != 0) + goto out; + } + + /* check if the replica's data was reloaded offline and we need + to reinitialize replica's changelog. This should be done + after the changelog is initialized */ + + replica_enumerate_replicas (replica_check_for_data_reload, NULL); + + /* register to be notified when backend state changes */ + slapi_register_backend_state_change((void *)multimaster_be_state_change, + multimaster_be_state_change); + + multimaster_started_flag = 1; + multimaster_stopped_flag = 0; + } +out: + return rc; +} + +int +multimaster_stop( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if (!multimaster_stopped_flag) + { + if (!is_ldif_dump) + { + agmtlist_shutdown(); /* Shut down replication agreements */ + } + + /* unregister backend state change notification */ + slapi_unregister_backend_state_change((void *)multimaster_be_state_change); + + changelog5_cleanup(); /* Shut down the changelog */ + multimaster_mtnode_extension_destroy(); /* Destroy mapping tree node exts */ + replica_destroy_name_hash(); /* destroy the hash and its remaining content */ + replica_config_destroy (); /* Destroy replica config info */ + multimaster_stopped_flag = 1; + /* JCMREPL - Wait for all our threads to stop */ + /* JCMREPL - Shut down the replication plugin */ + /* JCMREPL - Mark all the replication plugin interfaces at not enabled. */ + } + return rc; +} + + +PRBool +multimaster_started() +{ + return(multimaster_started_flag != 0); +} + + +/* + * Initialize the multimaster replication plugin. + */ +int replication_multimaster_plugin_init(Slapi_PBlock *pb) +{ + static int multimaster_initialised= 0; + int rc= 0; /* OK */ + void *identity = NULL; + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + repl_set_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION, identity); + + /* need the repl plugin path for the chain on update function */ +/* slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &entry); + PR_ASSERT(entry); + path = slapi_entry_attr_get_charptr(entry, "nsslapd-pluginpath"); + repl_set_repl_plugin_path(path); + slapi_ch_free_string(&path); +*/ + multimaster_mtnode_extension_init (); + + if(config_is_slapd_lite()) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "replication plugin not approved for restricted" + " mode Directory Server.\n" ); + rc= -1; + } + if(rc==0 && !multimaster_initialised) + { + /* initialize replica hash - has to be done before mapping tree is + initialized so we can't do it in the start function */ + + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replication_multimaster_plugin_init: failed to initialize replica hash\n"); + return -1; + } + + /* Initialize extensions */ + repl_con_init_ext(); + repl_sup_init_ext(); + + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterdesc ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) multimaster_start ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) multimaster_stop ); + + /* Register the plugin interfaces we implement */ + rc= slapi_register_plugin("preoperation", 1 /* Enabled */, "multimaster_preop_init", multimaster_preop_init, "Multimaster replication preoperation plugin", NULL, identity); + rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "multimaster_postop_init", multimaster_postop_init, "Multimaster replication postoperation plugin", NULL, identity); + rc= slapi_register_plugin("bepreoperation", 1 /* Enabled */, "multimaster_bepreop_init", multimaster_bepreop_init, "Multimaster replication bepreoperation plugin", NULL, identity); + rc= slapi_register_plugin("bepostoperation", 1 /* Enabled */, "multimaster_bepostop_init", multimaster_bepostop_init, "Multimaster replication bepostoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "multimaster_internalpreop_init", multimaster_internalpreop_init, "Multimaster replication internal preoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "multimaster_internalpostop_init", multimaster_internalpostop_init, "Multimaster replication internal postoperation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_start_extop_init", multimaster_start_extop_init, "Multimaster replication start extended operation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_end_extop_init", multimaster_end_extop_init, "Multimaster replication end extended operation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_total_extop_init", multimaster_total_extop_init, "Multimaster replication total update extended operation plugin", NULL, identity); + rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_response_extop_init", multimaster_response_extop_init, "Multimaster replication extended response plugin", NULL, identity); + } + return rc; +} diff --git a/ldap/servers/plugins/replication/repl5_mtnode_ext.c b/ldap/servers/plugins/replication/repl5_mtnode_ext.c new file mode 100644 index 00000000..e677927c --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_mtnode_ext.c @@ -0,0 +1,194 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_replica.c */ + +#include "repl.h" /* ONREPL - this is bad */ +#include "repl5.h" +#include "cl5_api.h" + +/* global data */ +static DataList *root_list; + +/* + * Mapping tree node extension management. Node stores replica object + */ + +void +multimaster_mtnode_extension_init () +{ + /* Initialize list that store node roots. It is used during + plugin startup to create replica objects */ + root_list = dl_new (); + dl_init (root_list, 0); +} + +void +multimaster_mtnode_extension_destroy () +{ + dl_cleanup (root_list, (FREEFN)slapi_sdn_free); + dl_free (&root_list); +} + +/* This function loops over the list of node roots, constructing replica objects + where exist */ +void +multimaster_mtnode_construct_replicas () +{ + Slapi_DN *root; + int cookie; + Replica *r; + mapping_tree_node *mtnode; + multimaster_mtnode_extension *ext; + + for (root = dl_get_first (root_list, &cookie); root; + root = dl_get_next (root_list, &cookie)) + { + r = replica_new(root); + if (r) + { + + mtnode = slapi_get_mapping_tree_node_by_dn(root); + if (mtnode == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "multimaster_mtnode_construct_replicas: " + "failed to locate mapping tree node for %s\n", + slapi_sdn_get_dn (root)); + continue; + } + + ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode); + if (ext == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "multimaster_mtnode_construct_replicas: " + "failed to locate replication extension of mapping tree node for %s\n", + slapi_sdn_get_dn (root)); + continue; + } + + ext->replica = object_new(r, replica_destroy); + if (replica_add_by_name (replica_get_name (r), ext->replica) != 0) + { + object_release (ext->replica); + ext->replica = NULL; + } + } + } +} + +void * +multimaster_mtnode_extension_constructor (void *object, void *parent) +{ + mapping_tree_node *node; + const Slapi_DN *root; + multimaster_mtnode_extension *ext; + + ext = (multimaster_mtnode_extension *)slapi_ch_calloc (1, sizeof (multimaster_mtnode_extension)); + + node = (mapping_tree_node *)object; + + /* replica can be attached only to local public data */ + if (slapi_mapping_tree_node_is_set (node, SLAPI_MTN_LOCAL) && + !slapi_mapping_tree_node_is_set (node, SLAPI_MTN_PRIVATE)) + { + root = slapi_get_mapping_tree_node_root (node); + /* ONREPL - we don't create replica object here because + we can't fully initialize replica here since backends + are not yet started. Instead, replica objects are created + during replication plugin startup */ + if (root) + { + /* for now just store node root in the root list */ + dl_add (root_list, slapi_sdn_dup (root)); + } + } + + return ext; +} + +void +multimaster_mtnode_extension_destructor (void* ext, void *object, void *parent) +{ + if (ext) + { + multimaster_mtnode_extension *mtnode_ext = (multimaster_mtnode_extension *)ext; + if (mtnode_ext->replica) + { + object_release (mtnode_ext->replica); + mtnode_ext->replica = NULL; + } + } +} + +Object * +replica_get_replica_from_dn (const Slapi_DN *dn) +{ + mapping_tree_node *mtnode; + multimaster_mtnode_extension *ext; + + if (dn == NULL) + return NULL; + + mtnode = slapi_get_mapping_tree_node_by_dn(dn); + if (mtnode == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_get_replica_from_dn: " + "failed to locate mapping tree node for %s\n", + slapi_sdn_get_dn (dn)); + return NULL; + } + + ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode); + if (ext == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_get_replica_from_dn: " + "failed to locate replication extension of mapping tree node for %s\n", + slapi_sdn_get_dn (dn)); + return NULL; + } + + if (ext->replica) + object_acquire (ext->replica); + + return ext->replica; +} + +Object *replica_get_replica_for_op (Slapi_PBlock *pb) +{ + char *dn; + Slapi_DN *sdn; + Object *repl_obj = NULL; + + if (pb) + { + /* get replica generation for this operation */ + slapi_pblock_get (pb, SLAPI_TARGET_DN, &dn); + sdn = slapi_sdn_new_dn_byref(dn); + repl_obj = replica_get_replica_from_dn (sdn); + + slapi_sdn_free (&sdn); + } + + return repl_obj; +} + +Object *replica_get_for_backend (const char *be_name) +{ + Slapi_Backend *be; + const Slapi_DN *suffix; + Object *r_obj; + + be = slapi_be_select_by_instance_name(be_name); + if (NULL == be) + return NULL; + + suffix = slapi_be_getsuffix(be, 0); + + r_obj = replica_get_replica_from_dn (suffix); + + return r_obj; +} diff --git a/ldap/servers/plugins/replication/repl5_plugins.c b/ldap/servers/plugins/replication/repl5_plugins.c new file mode 100644 index 00000000..afee321a --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_plugins.c @@ -0,0 +1,1416 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * repl5_consumer.c - consumer plugin functions + */ + +/* + * LDAP Data Model Constraints... + * + * 1) Parent entry must exist. + * 2) RDN must be unique. + * 3) RDN components must be attribute values. + * 4) Must conform to the schema constraints - Single Valued Attributes. + * 5) Must conform to the schema constraints - Required Attributes. + * 6) Must conform to the schema constraints - No Extra Attributes. + * 7) Duplicate attribute values are not permited. + * 8) Cycle in the ancestry graph not permitted. + * + * Update Resolution Procedures... + * 1) ... + * 2) Add the UniqueID to the RDN. + * 3) Remove the not present RDN component. + * Use the UniqueID if the RDN becomes empty. + * 4) Keep the most recent value. + * 5) Ignore. + * 6) Ignore. + * 7) Keep the largest CSN for the duplicate value. + * 8) Don't check for this. + */ + +#include "repl5.h" +#include "repl.h" +#include "cl5_api.h" +#include "urp.h" + +static char *local_purl = NULL; +static char *purl_attrs[] = {"nsslapd-localhost", "nsslapd-port", "nsslapd-secureport", NULL}; + +/* Forward declarations */ +static void copy_operation_parameters(Slapi_PBlock *pb); +static int write_changelog_and_ruv(Slapi_PBlock *pb); +static int process_postop (Slapi_PBlock *pb); +static int cancel_opcsn (Slapi_PBlock *pb); +static int ruv_tombstone_op (Slapi_PBlock *pb); +static PRBool process_operation (Slapi_PBlock *pb, const CSN *csn); +static PRBool is_mmr_replica (Slapi_PBlock *pb); +static const char *replica_get_purl_for_op (const Replica *r, Slapi_PBlock *pb, const CSN *opcsn); +static void strip_legacy_info (slapi_operation_parameters *op_params); +static void close_changelog_for_replica (Object *r_obj); +static void process_new_ruv_for_replica (Replica *r); + +/* + * XXXggood - what to do if both ssl and non-ssl ports available? How + * do you know which to use? Offer a choice in replication config? + */ +int +multimaster_set_local_purl() +{ + int rc = 0; + Slapi_Entry **entries; + Slapi_PBlock *pb = NULL; + + pb = slapi_pblock_new (); + + slapi_search_internal_set_pb (pb, "cn=config", LDAP_SCOPE_BASE, + "objectclass=*", purl_attrs, 0, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_set_local_purl: " + "unable to read server configuration: error %d\n", rc); + } + else + { + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || NULL == entries[0]) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_set_local_purl: " + "server configuration missing\n"); + rc = -1; + } + else + { + char *host = slapi_entry_attr_get_charptr(entries[0], "nsslapd-localhost"); + char *port = slapi_entry_attr_get_charptr(entries[0], "nsslapd-port"); + char *sslport = slapi_entry_attr_get_charptr(entries[0], "nsslapd-secureport"); + if (host == NULL || ((port == NULL && sslport == NULL))) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "multimaster_set_local_purl: invalid server " + "configuration\n"); + } + else + { + int len = 0; + char *patt = "ldap://%s:%s"; + len += strlen(host); + len += strlen(port); + len += strlen(patt); + len++; /* for \0 */ + local_purl = slapi_ch_malloc(len); + sprintf(local_purl, patt, host, port); + } + + /* slapi_ch_free acceptS NULL pointer */ + slapi_ch_free ((void**)&host); + slapi_ch_free ((void**)&port); + slapi_ch_free ((void**)&sslport); + } + } + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + + return rc; +} + + +const char * +multimaster_get_local_purl() +{ + return local_purl; +} + + +/* ================= Multimaster Pre-Op Plugin Points ================== */ + + +int +multimaster_preop_bind (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_preop_add (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + char *superior_uuid= NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, &superior_uuid, &csn, NULL /* modrdn_mods */); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control - Add\n", sessionid); + } + else if (1 == drc) + { + /* + * For add operations, we just set the operation csn. The entry's + * uniqueid should already be an attribute of the replicated entry. + */ + struct slapi_operation_parameters *op_params; + + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + slapi_ch_free ((void**)&superior_uuid); + + return -1; + } + + operation_set_csn(op, csn); + slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, target_uuid); + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + /* JCMREPL - Complain if there's no superior uuid */ + op_params->p.p_add.parentuniqueid= superior_uuid; /* JCMREPL - Not very elegant */ + /* JCMREPL - When do these things get free'd? */ + if(target_uuid!=NULL) + { + /* + * Make sure that the Unique Identifier in the Control is the + * same as the one in the entry. + */ + Slapi_Entry *addentry; + char *entry_uuid; + slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &addentry ); + entry_uuid= slapi_entry_attr_get_charptr( addentry, SLAPI_ATTR_UNIQUEID); + if(entry_uuid==NULL) + { + /* Odd that the entry doesn't have a Unique Identifier. But, we can fix it. */ + slapi_entry_set_uniqueid(addentry, slapi_ch_strdup(target_uuid)); /* JCMREPL - strdup EVIL! There should be a uuid dup function. */ + } + else + { + if(strcasecmp(entry_uuid,target_uuid)!=0) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s Replicated Add received with Control_UUID=%s and Entry_UUID=%s.\n", + sessionid, target_uuid,entry_uuid); + } + + slapi_ch_free ((void**)&entry_uuid); + } + } + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + + return 0; +} + +int +multimaster_preop_delete (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_replica_attr_handler ( op, (void*)replica_get_attr ); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, NULL, &csn, NULL /* modrdn_mods */); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control - Delete\n", sessionid); + } + else if (1 == drc) + { + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, + "%s replication operation not processed, replica unavailable " + "or csn ignored\n", sessionid); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + + return -1; + } + + /* + * For delete operations, we pass the uniqueid of the deleted entry + * to the backend and let it sort out which entry to really delete. + * We also set the operation csn. + */ + operation_set_csn(op, csn); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid); + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + slapi_operation_set_replica_attr_handler ( op, (void*)replica_get_attr ); + + return 0; +} + +int +multimaster_preop_modify (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, NULL, &csn, NULL /* modrdn_mods */); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control- Modify\n", sessionid); + } + else if (1 == drc) + { + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, + "%s replication operation not processed, replica unavailable " + "or csn ignored\n", sessionid); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + + return -1; + } + + /* + * For modify operations, we pass the uniqueid of the modified entry + * to the backend and let it sort out which entry to really modify. + * We also set the operation csn. + */ + operation_set_csn(op, csn); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid); + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + return 0; +} + +int +multimaster_preop_modrdn (Slapi_PBlock *pb) +{ + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* If there is no replica or it is a legacy consumer - we don't need to continue. + Legacy plugin is handling 4.0 consumer code */ + /* but if it is legacy, csngen_handler needs to be assigned here */ + is_legacy_operation = + operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + if (is_legacy_operation) + { + copy_operation_parameters(pb); + slapi_operation_set_csngen_handler(op, + (void*)replica_generate_next_csn); + return 0; + } + + if (!is_mmr_replica (pb)) + { + copy_operation_parameters(pb); + return 0; + } + + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + + if(is_replicated_operation) + { + if(!is_fixup_operation) + { + LDAPControl **ctrlp; + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_pblock_get(pb, SLAPI_REQCONTROLS, &ctrlp); + if (NULL != ctrlp) + { + CSN *csn = NULL; + char *target_uuid = NULL; + char *newsuperior_uuid = NULL; + LDAPMod **modrdn_mods = NULL; + int drc = decode_NSDS50ReplUpdateInfoControl(ctrlp, &target_uuid, &newsuperior_uuid, + &csn, &modrdn_mods); + if (-1 == drc) + { + slapi_log_error(SLAPI_LOG_FATAL, REPLICATION_SUBSYSTEM, + "%s An error occurred while decoding the replication update " + "control - ModRDN\n", sessionid); + } + else if (1 == drc) + { + /* + * For modrdn operations, we pass the uniqueid of the entry being + * renamed to the backend and let it sort out which entry to really + * rename. We also set the operation csn, and if the newsuperior_uuid + * was sent, we decode that as well. + */ + struct slapi_operation_parameters *op_params; + + /* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ + if (!process_operation (pb, csn)) + { + slapi_send_ldap_result(pb, LDAP_SUCCESS, 0, + "replication operation not processed, replica unavailable " + "or csn ignored", 0, 0); + csn_free (&csn); + slapi_ch_free ((void**)&target_uuid); + slapi_ch_free ((void**)&newsuperior_uuid); + ldap_mods_free (modrdn_mods, 1); + + return -1; + } + + operation_set_csn(op, csn); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, target_uuid); + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid= newsuperior_uuid; /* JCMREPL - Not very elegant */ + } + + /* + * The replicated modrdn operation may also contain a sequence + * that contains side effects of the modrdn operation, e.g. the + * modifiersname and modifytimestamp are updated. + */ + if (NULL != modrdn_mods) + { + LDAPMod **mods; + Slapi_Mods smods; + int i; + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + slapi_mods_init_passin(&smods, mods); + for (i = 0; NULL != modrdn_mods[i]; i++) + { + slapi_mods_add_ldapmod(&smods, modrdn_mods[i]); /* Does not copy mod */ + } + mods = slapi_mods_get_ldapmods_passout(&smods); + slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods); + slapi_mods_done(&smods); + slapi_ch_free((void **)&modrdn_mods); /* Free container only - contents are referred to by array "mods" */ + } + } + else + { + PR_ASSERT(0); /* JCMREPL - A Replicated Operation with no Repl Baggage control... What does that mean? */ + } + } + else + { + /* Replicated & Fixup Operation */ + } + } + else + { + slapi_operation_set_csngen_handler ( op, (void*)replica_generate_next_csn ); + } + + copy_operation_parameters(pb); + + return 0; +} + +int +multimaster_preop_search (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_preop_compare (Slapi_PBlock *pb) +{ + return 0; +} + +static void +purge_entry_state_information (Slapi_PBlock *pb) +{ + CSN *purge_csn; + Object *repl_obj; + Replica *replica; + + /* we don't want to trim RUV tombstone because we will + deadlock with ourself */ + if (ruv_tombstone_op (pb)) + return; + + repl_obj = replica_get_replica_for_op(pb); + if (NULL != repl_obj) + { + replica = object_get_data(repl_obj); + if (NULL != replica) + { + purge_csn = replica_get_purge_csn(replica); + } + if (NULL != purge_csn) + { + Slapi_Entry *e; + int optype; + + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype); + switch (optype) + { + case SLAPI_OPERATION_MODIFY: + slapi_pblock_get(pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); + break; + case SLAPI_OPERATION_MODRDN: + /* XXXggood - the following always gives a NULL entry - why? */ + slapi_pblock_get(pb, SLAPI_MODRDN_EXISTING_ENTRY, &e); + break; + case SLAPI_OPERATION_DELETE: + slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &e); + break; + default: + e = NULL; /* Don't purge on ADD - not needed */ + break; + } + if (NULL != e) + { + char csn_str[CSN_STRSIZE]; + entry_purge_state_information(e, purge_csn); + /* conntion is always null */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Purged state information from entry %s up to " + "CSN %s\n", slapi_entry_get_dn(e), + csn_as_string(purge_csn, PR_FALSE, csn_str)); + } + csn_free(&purge_csn); + } + object_release(repl_obj); + } +} + +int +multimaster_bepreop_add (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if (is_replicated_operation && !is_fixup_operation) + { + rc = urp_add_operation(pb); + } + + return rc; +} + +int +multimaster_bepreop_delete (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if(is_replicated_operation && !is_fixup_operation) + { + rc = urp_delete_operation(pb); + } + + return rc; +} + +int +multimaster_bepreop_modify (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if(is_replicated_operation && !is_fixup_operation) + { + rc = urp_modify_operation(pb); + } + + /* Clean up old state information */ + purge_entry_state_information(pb); + + return rc; +} + +int +multimaster_bepreop_modrdn (Slapi_PBlock *pb) +{ + int rc= 0; + Slapi_Operation *op; + int is_replicated_operation; + int is_fixup_operation; + int is_legacy_operation; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + is_fixup_operation= operation_is_flag_set(op,OP_FLAG_REPL_FIXUP); + is_legacy_operation= operation_is_flag_set(op,OP_FLAG_LEGACY_REPLICATION_DN); + + /* For replicated operations, apply URP algorithm */ + if(is_replicated_operation && !is_fixup_operation) + { + rc = urp_modrdn_operation(pb); + } + + /* Clean up old state information */ + purge_entry_state_information(pb); + + return rc; +} + +int +multimaster_bepostop_modrdn (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_bepostop_delete (Slapi_PBlock *pb) +{ + return 0; +} + +/* postop - write to changelog */ +int +multimaster_postop_bind (Slapi_PBlock *pb) +{ + return 0; +} + +int +multimaster_postop_add (Slapi_PBlock *pb) +{ + return process_postop(pb); +} + +int +multimaster_postop_delete (Slapi_PBlock *pb) +{ + int rc; + Slapi_Operation *op; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if ( ! operation_is_flag_set (op, OP_FLAG_REPL_FIXUP) ) + { + urp_post_delete_operation (pb); + } + rc = process_postop(pb); + return rc; +} + +int +multimaster_postop_modify (Slapi_PBlock *pb) +{ + return process_postop(pb); +} + +int +multimaster_postop_modrdn (Slapi_PBlock *pb) +{ + int rc; + Slapi_Operation *op; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if ( ! operation_is_flag_set (op, OP_FLAG_REPL_FIXUP) ) + { + urp_post_modrdn_operation (pb); + } + rc = process_postop(pb); + return rc; +} + + +/* Helper functions */ + +/* + * This function makes a copy of the operation parameters + * and stashes them in the consumer operation extension. + * This is where the 5.0 Change Log will get the operation + * details from. + */ +static void +copy_operation_parameters(Slapi_PBlock *pb) +{ + Slapi_Operation *op = NULL; + struct slapi_operation_parameters *op_params; + supplier_operation_extension *opext; + Object *repl_obj; + Replica *replica; + + repl_obj = replica_get_replica_for_op (pb); + + /* we are only interested in the updates to replicas */ + if (repl_obj) + { + /* we only save the original operation parameters for replicated operations + since client operations don't go through urp engine and pblock data can be logged */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + PR_ASSERT (op); + + replica = (Replica*)object_get_data (repl_obj); + PR_ASSERT (replica); + + opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op); + if (operation_is_flag_set(op,OP_FLAG_REPLICATED) && + !operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + slapi_pblock_get (pb, SLAPI_OPERATION_PARAMETERS, &op_params); + opext->operation_parameters= operation_parameters_dup(op_params); + } + + /* this condition is needed to avoid re-entering lock when + ruv state is updated */ + if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + /* save replica generation in case it changes */ + opext->repl_gen = replica_get_generation (replica); + } + + object_release (repl_obj); + } +} + +/* + * Helper function: update the RUV so that it reflects the + * locally-processed update. This is called for both replicated + * and non-replicated operations. + */ +static void +update_ruv_component(Replica *replica, CSN *opcsn, Slapi_PBlock *pb) +{ + PRBool legacy; + char *purl; + + if (!replica || !opcsn) + return; + + /* Replica configured, so update its ruv */ + legacy = replica_is_legacy_consumer (replica); + if (legacy) + purl = replica_get_legacy_purl (replica); + else + purl = (char*)replica_get_purl_for_op (replica, pb, opcsn); + + replica_update_ruv(replica, opcsn, purl); + + if (legacy) + { + slapi_ch_free ((void**)&purl); + } +} + +/* + * Write the changelog. Note: it is an error to call write_changelog_and_ruv() for fixup + * operations. The caller should avoid calling this function if the operation is + * a fixup op. + * Also update the ruv - we need to do this while we have the replica lock acquired + * so that the csn is written to the changelog and the ruv is updated with the csn + * in one atomic operation - if there is no changelog, we still need to update + * the ruv (e.g. for consumers) + */ +static int +write_changelog_and_ruv (Slapi_PBlock *pb) +{ + int rc; + slapi_operation_parameters *op_params = NULL; + Object *repl_obj; + int return_value = 0; + Replica *r; + + /* we only log changes for operations applied to a replica */ + repl_obj = replica_get_replica_for_op (pb); + if (repl_obj == NULL) + return 0; + + r = (Replica*)object_get_data (repl_obj); + PR_ASSERT (r); + + if (replica_is_flag_set (r, REPLICA_LOG_CHANGES) && + (cl5GetState () == CL5_STATE_OPEN)) + { + supplier_operation_extension *opext = NULL; + const char *repl_name; + char *repl_gen; + Slapi_Operation *op; + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op); + PR_ASSERT (opext); + + /* get replica generation and replica name to pass to the write function */ + repl_name = replica_get_name (r); + repl_gen = opext->repl_gen; + PR_ASSERT (repl_name && repl_gen); + + /* for replicated operations, we log the original, non-urp data which is + saved in the operation extension */ + if (operation_is_flag_set(op,OP_FLAG_REPLICATED)) + { + PR_ASSERT (opext->operation_parameters); + op_params = opext->operation_parameters; + } + else /* since client operations don't go through urp, we log the operation data in pblock */ + { + Slapi_Entry *e = NULL; + const char *uniqueid = NULL; + + slapi_pblock_get (pb, SLAPI_OPERATION_PARAMETERS, &op_params); + PR_ASSERT (op_params); + + /* need to set uniqueid operation parameter */ + /* we try to use the appropriate entry (pre op or post op) + depending on the type of operation (add, delete, modify) + However, in some cases, the backend operation may fail in + a non fatal way (e.g. attempt to remove an attribute value + that does not exist) but we still need to log the change. + So, the POST_OP entry may not have been set in the FE modify + code. In that case, we use the PRE_OP entry. + */ + slapi_pblock_get (pb, SLAPI_ENTRY_POST_OP, &e); + if ((e == NULL) || + (op_params->operation_type == SLAPI_OPERATION_DELETE)) + { + slapi_pblock_get (pb, SLAPI_ENTRY_PRE_OP, &e); + } + + PR_ASSERT (e); + + uniqueid = slapi_entry_get_uniqueid (e); + PR_ASSERT (uniqueid); + + op_params->target_address.uniqueid = slapi_ch_strdup (uniqueid); + } + + /* we might have stripped all the mods - in that case we do not + log the operation */ + if (op_params->operation_type != SLAPI_OPERATION_MODIFY || + op_params->p.p_modify.modify_mods != NULL) + { + if (cl5_is_diskfull() && !cl5_diskspace_is_available()) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "write_changelog_and_ruv: Skipped due to DISKFULL\n"); + return 0; + } + rc = cl5WriteOperation(repl_name, repl_gen, op_params, + !operation_is_flag_set(op, OP_FLAG_REPLICATED)); + if (rc != CL5_SUCCESS) + { + char csn_str[CSN_STRSIZE]; + /* ONREPL - log error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "write_changelog_and_ruv: can't add a change for " + "%s (uniqid: %s, optype: %u) to changelog csn %s\n", + op_params->target_address.dn, + op_params->target_address.uniqueid, + op_params->operation_type, + csn_as_string(op_params->csn, PR_FALSE, csn_str)); + return_value = 1; + } + } + + if (!operation_is_flag_set(op,OP_FLAG_REPLICATED)) + { + slapi_ch_free((void**)&op_params->target_address.uniqueid); + } + } + + /* + This was moved because we need to write the changelog and update + the ruv in one atomic operation - I have seen cases where the inc + protocol thread interrupts this thread between the time the changelog + is written and the ruv is updated - this causes confusion in several + places, especially in _cl5SkipReplayEntry since it cannot find the csn it + just read from the changelog in either the supplier or consumer ruv + */ + if (0 == return_value) { + Slapi_Operation *op; + CSN *opcsn; + + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + opcsn = operation_get_csn(op); + update_ruv_component(r, opcsn, pb); + } + + object_release (repl_obj); + return return_value; +} + +/* + * Postop processing - write the changelog if the operation resulted in + * an LDAP_SUCCESS result code, update the RUV, and notify the replication + * agreements about the change. + * If the result code is not LDAP_SUCCESS, then cancel the operation CSN. + */ +static int +process_postop (Slapi_PBlock *pb) +{ + int rc = LDAP_SUCCESS; + Slapi_Operation *op; + Slapi_Backend *be; + int is_replicated_operation = 0; + CSN *opcsn = NULL; + char sessionid[REPL_SESSION_ID_SIZE]; + + /* we just let fixup operations through */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op ); + if ((operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) || + (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY))) + { + return 0; + } + + /* ignore operations intended for chaining backends - they will be + replicated back to us or should be ignored anyway + replicated operations should be processed normally, as they should + be going to a local backend */ + is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED); + slapi_pblock_get(pb, SLAPI_BACKEND, &be); + if (!is_replicated_operation && + slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)) + { + return 0; + } + + get_repl_session_id (pb, sessionid, &opcsn); + + slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc); + /* + * Don't abandon writing changelog since we'd do everything + * possible to keep the changelog in sync with the backend + * db which was committed before this function was called. + * + * if (rc == LDAP_SUCCESS && !slapi_op_abandoned(pb)) + */ + if (rc == LDAP_SUCCESS) + { + rc = write_changelog_and_ruv(pb); + if (rc == 0) + { + agmtlist_notify_all(pb); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s process postop: error writing changelog and ruv\n", sessionid); + } + } + else if (opcsn) + { + rc = cancel_opcsn (pb); + + /* Don't try to get session id since conn is always null */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s process postop: canceling operation csn\n", sessionid); + } + + /* the target unique id is set in the modify_preop above, so + we need to free it */ + /* The following bunch of frees code does not belong to this place + * but rather to operation_free who should be responsible for calling + * operation_parameters_free and it doesn't. I guess this is like this + * because several crashes happened in the past regarding some opparams + * that were getting individually freed before they should. Whatever + * the case, this is not the place and we should make sure that this + * code gets removed for 5.next and substituted by the strategy (operation_free). + * For 5.0, such change is too risky, so this will be done here */ + if (is_replicated_operation) + { + slapi_operation_parameters *op_params = NULL; + int optype = 0; + /* target uid and csn are set for all repl operations. Free them */ + char *target_uuid = NULL; + slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &optype); + slapi_pblock_get(pb, SLAPI_TARGET_UNIQUEID, &target_uuid); + slapi_pblock_set(pb, SLAPI_TARGET_UNIQUEID, NULL); + slapi_ch_free((void**)&target_uuid); + if (optype == SLAPI_OPERATION_ADD) { + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + slapi_ch_free((void **) &op_params->p.p_add.parentuniqueid); + } + if (optype == SLAPI_OPERATION_MODRDN) { + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + slapi_ch_free((void **) &op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid); + } + } + if (NULL == opcsn) + opcsn = operation_get_csn(op); + if (opcsn) + csn_free(&opcsn); + + return rc; +} + + +/* + * Cancel an operation CSN. This removes it from any CSN pending lists. + * This function is called when a previously-generated CSN will not + * be needed, e.g. if the update operation produced an error. + */ +static int +cancel_opcsn (Slapi_PBlock *pb) +{ + Object *repl_obj; + Slapi_Operation *op = NULL; + + PR_ASSERT (pb); + + repl_obj = replica_get_replica_for_op (pb); + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + PR_ASSERT (op); + if (repl_obj) + { + Replica *r; + Object *gen_obj; + CSNGen *gen; + CSN *opcsn; + + r = (Replica*)object_get_data (repl_obj); + PR_ASSERT (r); + opcsn = operation_get_csn(op); + + if (!operation_is_flag_set(op,OP_FLAG_REPLICATED)) + { + /* get csn generator for the replica */ + gen_obj = replica_get_csngen (r); + PR_ASSERT (gen_obj); + gen = (CSNGen*)object_get_data (gen_obj); + + if (NULL != opcsn) + { + csngen_abort_csn (gen, operation_get_csn(op)); + } + + object_release (gen_obj); + } + else if (!operation_is_flag_set(op,OP_FLAG_REPL_FIXUP)) + { + Object *ruv_obj; + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + ruv_cancel_csn_inprogress ((RUV*)object_get_data (ruv_obj), opcsn); + object_release (ruv_obj); + } + + object_release (repl_obj); + } + + return 0; +} + + + +/* + * Return non-zero if the target entry DN is the DN of the RUV tombstone + * entry. + * The entry has rdn of nsuniqueid = ffffffff-ffffffff-ffffffff-ffffffff + */ +static int +ruv_tombstone_op (Slapi_PBlock *pb) +{ + char *uniqueid; + int rc; + + slapi_pblock_get (pb, SLAPI_TARGET_UNIQUEID, &uniqueid); + + rc = uniqueid && strcasecmp (uniqueid, RUV_STORAGE_ENTRY_UNIQUEID) == 0; + + return rc; +} + +/* we don't want to process replicated operations with csn smaller + than the corresponding csn in the consumer's ruv */ +static PRBool +process_operation (Slapi_PBlock *pb, const CSN *csn) +{ + Object *r_obj; + Replica *r; + Object *ruv_obj; + RUV *ruv; + int rc; + + r_obj = replica_get_replica_for_op(pb); + if (r_obj == NULL) + { + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s process_operation: " + "can't locate replica for the replicated operation\n", + sessionid ); + return PR_FALSE; + } + + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + + ruv = (RUV*)object_get_data (ruv_obj); + PR_ASSERT (ruv); + + rc = ruv_add_csn_inprogress (ruv, csn); + + object_release (ruv_obj); + object_release (r_obj); + + return (rc == RUV_SUCCESS); +} + +static PRBool +is_mmr_replica (Slapi_PBlock *pb) +{ + Object *r_obj; + Replica *r; + PRBool mmr; + + r_obj = replica_get_replica_for_op(pb); + if (r_obj == NULL) + { + return PR_FALSE; + } + + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + mmr = !replica_is_legacy_consumer (r); + + object_release (r_obj); + + return mmr; +} + +static const char *replica_get_purl_for_op (const Replica *r, Slapi_PBlock *pb, const CSN *opcsn) +{ + int is_replicated_op; + const char *purl; + + slapi_pblock_get(pb, SLAPI_IS_MMR_REPLICATED_OPERATION, &is_replicated_op); + + if (!is_replicated_op) + { + purl = multimaster_get_local_purl(); + } + else + { + /* Get the appropriate partial URL from the supplier RUV */ + Slapi_Connection *conn; + consumer_connection_extension *connext; + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + connext = (consumer_connection_extension *)repl_con_get_ext( + REPL_CON_EXT_CONN, conn); + if (NULL == connext || NULL == connext->supplier_ruv) + { + char sessionid[REPL_SESSION_ID_SIZE]; + get_repl_session_id (pb, sessionid, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s replica_get_purl_for_op: " + "cannot obtain consumer connection extension or supplier_ruv.\n", + sessionid); + } + else + { + purl = ruv_get_purl_for_replica(connext->supplier_ruv, + csn_get_replicaid(opcsn)); + } + } + + return purl; +} + +/* ONREPL at the moment, I decided not to trim copiedFrom and copyingFrom + attributes when sending operation to replicas. This is because, each + operation results in a state information stored in the database and + if we don't replay all operations we will endup with state inconsistency. + + Keeping the function just in case + */ +static void strip_legacy_info (slapi_operation_parameters *op_params) +{ + switch (op_params->operation_type) + { + case SLAPI_OPERATION_ADD: + slapi_entry_delete_values_sv(op_params->p.p_add.target_entry, + type_copiedFrom, NULL); + slapi_entry_delete_values_sv(op_params->p.p_add.target_entry, + type_copyingFrom, NULL); + break; + case SLAPI_OPERATION_MODIFY: + { + Slapi_Mods smods; + LDAPMod *mod; + + slapi_mods_init_byref(&smods, op_params->p.p_modify.modify_mods); + mod = slapi_mods_get_first_mod(&smods); + while (mod) + { + /* modify just to update copiedFrom or copyingFrom attribute + does not contain modifiersname or modifytime - so we don't + have to strip them */ + if (strcasecmp (mod->mod_type, type_copiedFrom) == 0 || + strcasecmp (mod->mod_type, type_copyingFrom) == 0) + slapi_mods_remove(&smods); + mod = slapi_mods_get_next_mod(&smods); + } + + op_params->p.p_modify.modify_mods = slapi_mods_get_ldapmods_passout (&smods); + slapi_mods_done (&smods); + + break; + } + + default: break; + } +} + +/* this function is called when state of a backend changes */ +void +multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state) +{ + Object *r_obj; + Replica *r; + + /* check if we have replica associated with the backend */ + r_obj = replica_get_for_backend (be_name); + if (r_obj == NULL) + return; + + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + if (new_be_state == SLAPI_BE_STATE_ON) + { + /* backend came back online - restart replication */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: " + "replica %s is coming online; enabling replication\n", + slapi_sdn_get_ndn (replica_get_root (r))); + replica_enable_replication (r); + } + else if (new_be_state == SLAPI_BE_STATE_OFFLINE) + { + /* backend is about to be taken down - disable replication */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: " + "replica %s is going offline; disabling replication\n", + slapi_sdn_get_ndn (replica_get_root (r))); + replica_disable_replication (r, r_obj); + } + else if (new_be_state == SLAPI_BE_STATE_DELETE) + { + /* backend is about to be removed - disable replication */ + if (old_be_state == SLAPI_BE_STATE_ON) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "multimaster_be_state_change: " + "replica %s is about to be deleted; disabling replication\n", + slapi_sdn_get_ndn (replica_get_root (r))); + replica_disable_replication (r, r_obj); + } + } + + object_release (r_obj); +} + +static void +close_changelog_for_replica (Object *r_obj) +{ + if (cl5GetState () == CL5_STATE_OPEN) + cl5CloseDB (r_obj); +} diff --git a/ldap/servers/plugins/replication/repl5_prot_private.h b/ldap/servers/plugins/replication/repl5_prot_private.h new file mode 100644 index 00000000..fbaf96e4 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_prot_private.h @@ -0,0 +1,66 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef _REPL5_PROT_PRIVATE_H_ +#define _REPL5_PROT_PRIVATE_H_ + +#define ACQUIRE_SUCCESS 101 +#define ACQUIRE_REPLICA_BUSY 102 +#define ACQUIRE_FATAL_ERROR 103 +#define ACQUIRE_CONSUMER_WAS_UPTODATE 104 +#define ACQUIRE_TRANSIENT_ERROR 105 + +typedef struct private_repl_protocol +{ + void (*delete)(struct private_repl_protocol **); + void (*run)(struct private_repl_protocol *); + int (*stop)(struct private_repl_protocol *); + int (*status)(struct private_repl_protocol *); + void (*notify_update)(struct private_repl_protocol *); + void (*notify_agmt_changed)(struct private_repl_protocol *); + void (*notify_window_opened)(struct private_repl_protocol *); + void (*notify_window_closed)(struct private_repl_protocol *); + void (*update_now)(struct private_repl_protocol *); + PRLock *lock; + PRCondVar *cvar; + int stopped; + int terminate; + PRUint32 eventbits; + Repl_Connection *conn; + int last_acquire_response_code; + Repl_Agmt *agmt; + Object *replica_object; + void *private; + PRBool replica_acquired; +} Private_Repl_Protocol; + +extern Private_Repl_Protocol *Repl_5_Inc_Protocol_new(); +extern Private_Repl_Protocol *Repl_5_Tot_Protocol_new(); + +#define PROTOCOL_TERMINATION_NORMAL 301 +#define PROTOCOL_TERMINATION_ABNORMAL 302 +#define PROTOCOL_TERMINATION_NEEDS_TOTAL_UPDATE 303 + +#define RESUME_DO_TOTAL_UPDATE 401 +#define RESUME_DO_INCREMENTAL_UPDATE 402 +#define RESUME_TERMINATE 403 +#define RESUME_SUSPEND 404 + +/* Backoff timer settings for reconnect */ +#define PROTOCOL_BACKOFF_MINIMUM 3 /* 3 seconds */ +#define PROTOCOL_BACKOFF_MAXIMUM (60 * 5) /* 5 minutes */ +/* Backoff timer settings for replica busy reconnect */ +#define PROTOCOL_BUSY_BACKOFF_MINIMUM PROTOCOL_BACKOFF_MINIMUM +#define PROTOCOL_BUSY_BACKOFF_MAXIMUM PROTOCOL_BUSY_BACKOFF_MINIMUM + +/* protocol related functions */ +void release_replica(Private_Repl_Protocol *prp); +int acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv); +BerElement *entry2bere(const Slapi_Entry *e); +CSN *get_current_csn(Slapi_DN *replarea_sdn); +char* protocol_response2string (int response); + +#endif /* _REPL5_PROT_PRIVATE_H_ */ diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c new file mode 100644 index 00000000..725bf3f2 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_protocol.c @@ -0,0 +1,502 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_protocol.c */ +/* + + The replication protocol object manages the replication protocol for + a given replica. It determines which protocol(s) are appropriate to + use when updating a given replica. It also knows how to arbitrate + incremental and total update protocols for a given replica. + +*/ + +#include "repl5.h" +#include "repl5_prot_private.h" + +#define PROTOCOL_5_INCREMENTAL 1 +#define PROTOCOL_5_TOTAL 2 +#define PROTOCOL_4_INCREMENTAL 3 +#define PROTOCOL_4_TOTAL 4 + +typedef struct repl_protocol +{ + Private_Repl_Protocol *prp_incremental; /* inc protocol to use */ + Private_Repl_Protocol *prp_total; /* total protocol to use */ + Private_Repl_Protocol *prp_active_protocol; /* Pointer to active protocol */ + Repl_Agmt *agmt; /* The replication agreement we're servicing */ + Repl_Connection *conn; /* Connection to remote server */ + Object *replica_object; /* Local replica. If non-NULL, replica object is acquired */ + int state; + int next_state; + PRLock *lock; +} repl_protocol; + + +/* States */ +#define STATE_FINISHED 503 +#define STATE_BAD_STATE_SHOULD_NEVER_HAPPEN 599 + +/* Forward declarations */ +static Private_Repl_Protocol *private_protocol_factory(Repl_Protocol *rp, int type); + + + + +/* + * Create a new protocol instance. + */ +Repl_Protocol * +prot_new(Repl_Agmt *agmt, int protocol_state) +{ + Slapi_DN *replarea_sdn = NULL; + Repl_Protocol *rp = (Repl_Protocol *)slapi_ch_malloc(sizeof(Repl_Protocol)); + + rp->prp_incremental = rp->prp_total = rp->prp_active_protocol = NULL; + if (protocol_state == STATE_PERFORMING_TOTAL_UPDATE) + { + rp->state = STATE_PERFORMING_TOTAL_UPDATE; + } + else + { + rp->state = STATE_PERFORMING_INCREMENTAL_UPDATE; + } + rp->next_state = STATE_PERFORMING_INCREMENTAL_UPDATE; + if ((rp->lock = PR_NewLock()) == NULL) + { + goto loser; + } + rp->agmt = agmt; + if ((rp->conn = conn_new(agmt)) == NULL) + { + goto loser; + } + /* Acquire the local replica object */ + replarea_sdn = agmt_get_replarea(agmt); + rp->replica_object = replica_get_replica_from_dn(replarea_sdn); + if (NULL == rp->replica_object) + { + /* Whoa, no local replica!?!? */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to locate replica object for local replica %s\n", + agmt_get_long_name(agmt), + slapi_sdn_get_dn(replarea_sdn)); + goto loser; + } + rp->prp_incremental = private_protocol_factory(rp, PROTOCOL_5_INCREMENTAL); + rp->prp_total = private_protocol_factory(rp, PROTOCOL_5_TOTAL); + /* XXXggood register callback handlers for entries updated, and + schedule window enter/leave. */ + slapi_sdn_free(&replarea_sdn); + + return rp; +loser: + prot_delete(&rp); + return NULL; +} + + + + + +Object * +prot_get_replica_object(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + return rp->replica_object; +} + + + + + +Repl_Agmt * +prot_get_agreement(Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return NULL; + return rp->agmt; +} + + + + +void +prot_free(Repl_Protocol **rpp) +{ + Repl_Protocol *rp = NULL; + + if (rpp == NULL || *rpp == NULL) return; + + rp = *rpp; + + PR_Lock(rp->lock); + if (NULL != rp->prp_incremental) + { + rp->prp_incremental->delete(&rp->prp_incremental); + } + if (NULL != rp->prp_total) + { + rp->prp_total->delete(&rp->prp_total); + } + if (NULL != rp->replica_object) + { + object_release(rp->replica_object); + } + if (NULL != rp->conn) + { + conn_delete(rp->conn); + } + rp->prp_active_protocol = NULL; + PR_Unlock(rp->lock); + slapi_ch_free((void **)rpp); +} + +/* + * Destroy a protocol instance XXXggood not complete + */ +void +prot_delete(Repl_Protocol **rpp) +{ + Repl_Protocol *rp; + + PR_ASSERT(NULL != rpp); + rp = *rpp; + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL != rp) + { + prot_stop(rp); + prot_free(rpp); + } +} + + + + + +/* + * Get the connection object. + */ +Repl_Connection * +prot_get_connection(Repl_Protocol *rp) +{ + Repl_Connection *return_value; + + PR_ASSERT(NULL != rp); + PR_Lock(rp->lock); + return_value = rp->conn; + PR_Unlock(rp->lock); + return return_value; +} + + + + +/* + * This function causes the total protocol to start. + * This is accomplished by registering a state transition + * to a new state, and then signaling the incremental + * protocol to stop. + */ +void +prot_initialize_replica(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + + PR_Lock(rp->lock); + /* check that total protocol is not running */ + rp->next_state = STATE_PERFORMING_TOTAL_UPDATE; + /* Stop the incremental protocol, if running */ + rp->prp_incremental->stop(rp->prp_incremental); + if (rp->prp_total) agmt_set_last_init_status(rp->prp_total->agmt, 0, 0, NULL); + PR_Unlock(rp->lock); +} + + + + + +/* + * Main thread for protocol manager. + +This is a simple state machine. State transition table: + +Initial state: incremental update + +STATE EVENT NEXT STATE +----- ----- ---------- +incremental update shutdown finished +incremental update total update requested total update +total update shutdown finished +total update update complete incremental update +finished (any) finished + +*/ + +static void +prot_thread_main(void *arg) +{ + Repl_Protocol *rp = (Repl_Protocol *)arg; + int done; + + PR_ASSERT(NULL != rp); + + if (rp->agmt) { + set_thread_private_agmtname (agmt_get_long_name(rp->agmt)); + } + + done = 0; + + while (!done) + { + switch (rp->state) + { + case STATE_PERFORMING_INCREMENTAL_UPDATE: + /* Run the incremental update protocol */ + PR_Lock(rp->lock); + dev_debug("prot_thread_main(STATE_PERFORMING_INCREMENTAL_UPDATE): begin"); + rp->prp_active_protocol = rp->prp_incremental; + PR_Unlock(rp->lock); + rp->prp_incremental->run(rp->prp_incremental); + dev_debug("prot_thread_main(STATE_PERFORMING_INCREMENTAL_UPDATE): end"); + break; + case STATE_PERFORMING_TOTAL_UPDATE: + PR_Lock(rp->lock); + + /* stop incremental protocol if running */ + rp->prp_active_protocol = rp->prp_total; + /* After total protocol finished, return to incremental */ + rp->next_state = STATE_PERFORMING_INCREMENTAL_UPDATE; + PR_Unlock(rp->lock); + /* Run the total update protocol */ + dev_debug("prot_thread_main(STATE_PERFORMING_TOTAL_UPDATE): begin"); + rp->prp_total->run(rp->prp_total); + dev_debug("prot_thread_main(STATE_PERFORMING_TOTAL_UPDATE): end"); + /* update the agreement entry to notify clients that + replica initialization is completed. */ + agmt_replica_init_done (rp->agmt); + + break; + case STATE_FINISHED: + dev_debug("prot_thread_main(STATE_FINISHED): exiting prot_thread_main"); + done = 1; + break; + } + rp->state = rp->next_state; + } +} + + +/* + * Start a thread to handle the replication protocol. + */ +void +prot_start(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + if (NULL != rp) + { + if (PR_CreateThread(PR_USER_THREAD, prot_thread_main, (void *)rp, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL) + { + PRErrorCode prerr = PR_GetError(); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to create protocol thread; NSPR error - %d, %s\n", + agmt_get_long_name(rp->agmt), + prerr, slapd_pr_strerror(prerr)); + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to start " + "protocol object - NULL protocol object passed to prot_start.\n"); + } +} + + + + + +/* + * Stop a protocol instance. + */ +void +prot_stop(Repl_Protocol *rp) +{ + PR_ASSERT(NULL != rp); + if (NULL != rp) + { + PR_Lock(rp->lock); + rp->next_state = STATE_FINISHED; + if (NULL != rp->prp_incremental) + { + if (rp->prp_incremental->stop(rp->prp_incremental) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Warning: incremental protocol for replica \"%s\" " + "did not shut down properly.\n", + agmt_get_long_name(rp->agmt)); + } + } + if (NULL != rp->prp_total) + { + if (rp->prp_total->stop(rp->prp_total) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Warning: total protocol for replica \"%s\" " + "did not shut down properly.\n", + agmt_get_long_name(rp->agmt)); + } + } + PR_Unlock(rp->lock); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: prot_stop() " + " called on NULL protocol instance.\n"); + } +} + + + + + +/* + * Call the notify_update method of the incremental or total update + * protocol, is either is active. + */ +void +prot_notify_update(Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return; + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_update(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +/* + * Call the notify_agmt_changed method of the incremental or total update + * protocol, is either is active. + */ +void +prot_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Replication agreement for %s could not be updated. " + "For replication to take place, please enable the suffix " + "and restart the server\n", agmt_name); + return; + } + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_agmt_changed(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +void +prot_notify_window_opened (Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return; + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_window_opened(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +void +prot_notify_window_closed (Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL == rp) return; + + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + rp->prp_active_protocol->notify_window_closed(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); +} + + +int +prot_status(Repl_Protocol *rp) +{ + int return_status = PROTOCOL_STATUS_UNKNOWN; + + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + if (NULL != rp) + { + PR_Lock(rp->lock); + if (NULL != rp->prp_active_protocol) + { + return_status = rp->prp_active_protocol->status(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); + } + return return_status; +} + + +/* + * Start an incremental protocol session, even if we're not + * currently in a schedule window. + * If the total protocol is active, do nothing. + * Otherwise, notify the incremental protocol that it should + * run once. + */ +void +prot_replicate_now(Repl_Protocol *rp) +{ + /* MAB: rp might be NULL for disabled suffixes. Don't ASSERT on it */ + + if (NULL != rp) + { + PR_Lock(rp->lock); + if (rp->prp_incremental == rp->prp_active_protocol) + { + rp->prp_active_protocol->update_now(rp->prp_active_protocol); + } + PR_Unlock(rp->lock); + } +} + +/* + * A little factory function to create a protocol + * instance of the correct type. + */ +static Private_Repl_Protocol * +private_protocol_factory(Repl_Protocol *rp, int type) +{ + Private_Repl_Protocol *prp; + switch (type) + { + case PROTOCOL_5_INCREMENTAL: + prp = Repl_5_Inc_Protocol_new(rp); + break; + case PROTOCOL_5_TOTAL: + prp = Repl_5_Tot_Protocol_new(rp); + break; + } + return prp; +} diff --git a/ldap/servers/plugins/replication/repl5_protocol_util.c b/ldap/servers/plugins/replication/repl5_protocol_util.c new file mode 100644 index 00000000..16f65485 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_protocol_util.c @@ -0,0 +1,468 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_protocol_util.c */ +/* + +Code common to both incremental and total protocols. + +*/ + +#include "repl5.h" +#include "repl5_prot_private.h" + + +/* + * Obtain a current CSN (e.g. one that would have been + * generated for an operation occurring at this time) + * for a given replica. + */ +CSN * +get_current_csn(Slapi_DN *replarea_sdn) +{ + Object *replica_obj; + Replica *replica; + Object *gen_obj; + CSNGen *gen; + CSN *current_csn = NULL; + + if (NULL != replarea_sdn) + { + replica_obj = replica_get_replica_from_dn(replarea_sdn); + if (NULL != replica_obj) + { + replica = object_get_data(replica_obj); + if (NULL != replica) + { + gen_obj = replica_get_csngen(replica); + if (NULL != gen_obj) + { + gen = (CSNGen *)object_get_data(gen_obj); + if (NULL != gen) + { + if (csngen_new_csn(gen, ¤t_csn, + PR_FALSE /* notify */) != CSN_SUCCESS) + { + current_csn = NULL; + + } + object_release(gen_obj); + } + } + } + } + } + return current_csn; +} + + +/* + * Acquire exclusive access to a replica. Send a start replication extended + * operation to the replica. The response will contain a success code, and + * optionally the replica's update vector if acquisition is successful. + * This function returns one of the following: + * ACQUIRE_SUCCESS - the replica was acquired, and we have exclusive update access + * ACQUIRE_REPLICA_BUSY - another master was updating the replica + * ACQUIRE_FATAL_ERROR - something bad happened, and it's not likely to improve + * if we wait. + * ACQUIRE_TRANSIENT_ERROR - something bad happened, but it's probably worth + * another try after waiting a while. + * If ACQUIRE_SUCCESS is returned, then ruv will point to the replica's update + * vector. It's possible that the replica does something goofy and doesn't + * return us an update vector, so be prepared for ruv to be NULL (but this is + * an error). + */ +int +acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv) +{ + int return_value; + ConnResult crc; + Repl_Connection *conn; + + PR_ASSERT(prp && prot_oid); + + if (prp->replica_acquired) /* we already acquire replica */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Remote replica already acquired\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + return ACQUIRE_SUCCESS; + } + + if (NULL != ruv) + { + ruv_destroy ( ruv ); + } + + if (strcmp(prot_oid, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID) == 0) + { + Replica *replica; + Object *supl_ruv_obj, *cons_ruv_obj; + PRBool is_newer = PR_FALSE; + + object_acquire(prp->replica_object); + replica = object_get_data(prp->replica_object); + supl_ruv_obj = replica_get_ruv ( replica ); + cons_ruv_obj = agmt_get_consumer_ruv ( prp->agmt ); + is_newer = ruv_is_newer ( supl_ruv_obj, cons_ruv_obj ); + if ( supl_ruv_obj ) object_release ( supl_ruv_obj ); + if ( cons_ruv_obj ) object_release ( cons_ruv_obj ); + object_release (prp->replica_object); + replica = NULL; + + if (is_newer == PR_FALSE) { + prp->last_acquire_response_code = NSDS50_REPL_UPTODATE; + return ACQUIRE_CONSUMER_WAS_UPTODATE; + } + } + + prp->last_acquire_response_code = NSDS50_REPL_REPLICA_NO_RESPONSE; + + /* Get the connection */ + conn = prp->conn; + + crc = conn_connect(conn); + if (CONN_OPERATION_FAILED == crc) + { + return_value = ACQUIRE_TRANSIENT_ERROR; + } + else if (CONN_SSL_NOT_ENABLED == crc) + { + return_value = ACQUIRE_FATAL_ERROR; + } + else + { + /* we don't want the timer to go off in the middle of an operation */ + conn_cancel_linger(conn); + /* Does the remote replica support the 5.0 protocol? */ + crc = conn_replica_supports_ds5_repl(conn); + if (CONN_DOES_NOT_SUPPORT_DS5_REPL == crc) + { + return_value = ACQUIRE_FATAL_ERROR; + } + else if (CONN_NOT_CONNECTED == crc || CONN_OPERATION_FAILED == crc) + { + /* We don't know anything about the remote replica. Try again later. */ + return_value = ACQUIRE_TRANSIENT_ERROR; + } + else + { + /* Good to go. Start the protocol. */ + CSN *current_csn = NULL; + struct berval *retdata = NULL; + char *retoid = NULL; + Slapi_DN *replarea_sdn; + + /* Obtain a current CSN */ + replarea_sdn = agmt_get_replarea(prp->agmt); + current_csn = get_current_csn(replarea_sdn); + if (NULL != current_csn) + { + struct berval *payload = NSDS50StartReplicationRequest_new( + prot_oid, slapi_sdn_get_ndn(replarea_sdn), + NULL /* XXXggood need to provide referral(s) */, current_csn); + /* JCMREPL - Need to extract the referrals from the RUV */ + csn_free(¤t_csn); + current_csn = NULL; + crc = conn_send_extended_operation(conn, + REPL_START_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid, + &retdata, NULL /* update control */, NULL /* returned controls */); + ber_bvfree(payload); + payload = NULL; + /* Look at the response we got. */ + if (CONN_OPERATION_SUCCESS == crc) + { + /* + * Extop was processed. Look at extop response to see if we're + * permitted to go ahead. + */ + struct berval **ruv_bervals = NULL; + int extop_result; + int extop_rc = decode_repl_ext_response(retdata, &extop_result, + &ruv_bervals); + if (0 == extop_rc) + { + prp->last_acquire_response_code = extop_result; + switch (extop_result) + { + /* XXXggood handle other error codes here */ + case NSDS50_REPL_INTERNAL_ERROR: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: " + "an internal error occurred on the remote replica. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_PERMISSION_DENIED: + /* Not allowed to send updates */ + { + char *repl_binddn = agmt_get_binddn(prp->agmt); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: permission denied. " + "The bind dn \"%s\" does not have permission to " + "supply replication updates to the replica. " + "Will retry later.\n", + agmt_get_long_name(prp->agmt), repl_binddn); + slapi_ch_free((void **)&repl_binddn); + return_value = ACQUIRE_TRANSIENT_ERROR; + break; + } + case NSDS50_REPL_NO_SUCH_REPLICA: + /* There is no such replica on the consumer */ + { + Slapi_DN *repl_root = agmt_get_replarea(prp->agmt); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: there is no " + "replicated area \"%s\" on the consumer server. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt), + slapi_sdn_get_dn(repl_root)); + slapi_sdn_free(&repl_root); + return_value = ACQUIRE_FATAL_ERROR; + break; + } + case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW: + /* Large clock skew between the consumer and the supplier */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: " + "Excessive clock skew between the supplier and " + "the consumer. Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_DECODING_ERROR: + /* We sent something the replica couldn't understand. */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: " + "the consumer was unable to decode the " + "startReplicationRequest extended operation sent by the " + "supplier. Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_REPLICA_BUSY: + /* Someone else is updating the replica. Try later. */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Unable to acquire replica: " + "the replica is currently being updated" + "by another supplier. Will try later\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_REPLICA_BUSY; + break; + case NSDS50_REPL_LEGACY_CONSUMER: + /* remote replica is a legacy consumer */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to acquire replica: the replica " + "is supplied by a legacy supplier. " + "Replication is aborting.\n", agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_REPLICAID_ERROR: + /* remote replica detected a duplicate ReplicaID */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to aquire replica: the replica " + "has the same Replica ID as this one. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + break; + case NSDS50_REPL_REPLICA_READY: + /* We've acquired the replica. */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Replica was successfully acquired.\n", + agmt_get_long_name(prp->agmt)); + /* Parse the update vector */ + if (NULL != ruv_bervals && NULL != ruv) + { + if (ruv_init_from_bervals(ruv_bervals, ruv) != RUV_SUCCESS) + { + /* Couldn't parse the update vector */ + *ruv = NULL; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: acquired replica, " + "but could not parse update vector. " + "The replica must be reinitialized.\n", + agmt_get_long_name(prp->agmt)); + } + } + + /* Save consumer's RUV in the replication agreement. + It is used by the changelog trimming code */ + if (ruv && *ruv) + agmt_set_consumer_ruv (prp->agmt, *ruv); + + return_value = ACQUIRE_SUCCESS; + break; + default: + return_value = ACQUIRE_FATAL_ERROR; + } + } + else + { + /* Couldn't parse the response */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to parse the response to the " + "startReplication extended operation. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + prp->last_acquire_response_code = NSDS50_REPL_INTERNAL_ERROR; + return_value = ACQUIRE_FATAL_ERROR; + } + if (NULL != ruv_bervals) + ber_bvecfree(ruv_bervals); + } + else + { + int operation, error; + conn_get_error(conn, &operation, &error); + + /* Couldn't send the extended operation */ + return_value = ACQUIRE_TRANSIENT_ERROR; /* XXX right return value? */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to send a startReplication " + "extended operation to consumer (%s). Will retry later.\n", + agmt_get_long_name(prp->agmt), + error ? ldap_err2string(error) : "unknown error"); + } + } + else + { + /* Couldn't get a current CSN */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to obtain current CSN. " + "Replication is aborting.\n", + agmt_get_long_name(prp->agmt)); + return_value = ACQUIRE_FATAL_ERROR; + } + slapi_sdn_free(&replarea_sdn); + if (NULL != retoid) + ldap_memfree(retoid); + if (NULL != retdata) + ber_bvfree(retdata); + } + } + + if (ACQUIRE_SUCCESS != return_value) + { + /* could not acquire the replica, so reinstate the linger timer, since this + means we won't call release_replica, which also reinstates the timer */ + conn_start_linger(conn); + } + else + { + /* replica successfully acquired */ + prp->replica_acquired = PR_TRUE; + } + + return return_value; +} + + +/* + * Release a replica by sending an "end replication" extended request. + */ +void +release_replica(Private_Repl_Protocol *prp) +{ + int rc; + struct berval *retdata = NULL; + char *retoid = NULL; + struct berval *payload = NULL; + Slapi_DN *replarea_sdn = NULL; + + PR_ASSERT(NULL != prp); + PR_ASSERT(NULL != prp->conn); + + if (!prp->replica_acquired) + return; + + replarea_sdn = agmt_get_replarea(prp->agmt); + payload = NSDS50EndReplicationRequest_new((char *)slapi_sdn_get_dn(replarea_sdn)); /* XXXggood had to cast away const */ + slapi_sdn_free(&replarea_sdn); + rc = conn_send_extended_operation(prp->conn, + REPL_END_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid, + &retdata, NULL /* update control */, NULL /* returned controls */); + if (0 != rc) + { + int operation, error; + conn_get_error(prp->conn, &operation, &error); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: unable to send endReplication extended operation (%s)\n", + agmt_get_long_name(prp->agmt), + error ? ldap_err2string(error) : "unknown error"); + } + else + { + struct berval **ruv_bervals = NULL; /* Shouldn't actually be returned */ + int extop_result; + int extop_rc = decode_repl_ext_response(retdata, &extop_result, + (struct berval ***)&ruv_bervals); + if (0 == extop_rc) + { + if (NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED == extop_result) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Successfully released consumer\n", agmt_get_long_name(prp->agmt)); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Unable to release consumer: response code %d\n", + agmt_get_long_name(prp->agmt), extop_result); + /* disconnect from the consumer so that it does not stay locked */ + conn_disconnect (prp->conn); + } + } + else + { + /* Couldn't parse the response */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Warning: Unable to parse the response " + " to the endReplication extended operation.\n", + agmt_get_long_name(prp->agmt)); + } + if (NULL != ruv_bervals) + ber_bvecfree(ruv_bervals); + /* XXXggood free ruv_bervals if we got them for some reason */ + } + if (NULL != payload) + ber_bvfree(payload); + if (NULL != retoid) + ldap_memfree(retoid); + if (NULL != retdata) + ber_bvfree(retdata); + + /* replica is released, start the linger timer on the connection, which + was stopped in acquire_replica */ + conn_start_linger(prp->conn); + + prp->replica_acquired = PR_FALSE; +} + +/* converts consumer's response to a string */ +char * +protocol_response2string (int response) +{ + switch (response) + { + case NSDS50_REPL_REPLICA_READY: return "replica acquired"; + case NSDS50_REPL_REPLICA_BUSY: return "replica busy"; + case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW: return "excessive clock skew"; + case NSDS50_REPL_PERMISSION_DENIED: return "permission denied"; + case NSDS50_REPL_DECODING_ERROR: return "decoding error"; + case NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL: return "unknown update protocol"; + case NSDS50_REPL_NO_SUCH_REPLICA: return "no such replica"; + case NSDS50_REPL_BELOW_PURGEPOINT: return "csn below purge point"; + case NSDS50_REPL_INTERNAL_ERROR: return "internal error"; + case NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED: return "replica released"; + case NSDS50_REPL_LEGACY_CONSUMER: return "replica is a legacy consumer"; + case NSDS50_REPL_REPLICAID_ERROR: return "duplicate replica ID detected"; + case NSDS50_REPL_UPTODATE: return "no change to send"; + default: return "unknown error"; + } +} diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c new file mode 100644 index 00000000..5bc3e8ee --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica.c @@ -0,0 +1,3387 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_replica.c */ + +#include "slapi-plugin.h" +#include "repl.h" /* ONREPL - this is bad */ +#include "repl5.h" +#include "repl_shared.h" +#include "csnpl.h" +#include "cl5_api.h" + +/* from proto-slap.h */ +int g_get_shutdown(); + +#define RUV_SAVE_INTERVAL (30 * 1000) /* 30 seconds */ +#define START_UPDATE_DELAY 2 /* 2 second */ +#define START_REAP_DELAY 3600 /* 1 hour */ + +#define REPLICA_RDN "cn=replica" +#define CHANGELOG_RDN "cn=legacy changelog" + +/* + * A replica is a locally-held copy of a portion of the DIT. + */ +struct replica { + Slapi_DN *repl_root; /* top of the replicated area */ + char *repl_name; /* unique replica name */ + PRBool new_name; /* new name was generated - need to be saved */ + ReplicaUpdateDNList updatedn_list; /* list of dns with which a supplier should bind + to update this replica */ + ReplicaType repl_type; /* is this replica read-only ? */ + PRBool legacy_consumer; /* if true, this replica is supplied by 4.0 consumer */ + char* legacy_purl; /* partial url of the legacy supplier */ + ReplicaId repl_rid; /* replicaID */ + Object *repl_ruv; /* replica update vector */ + PRBool repl_ruv_dirty; /* Dirty flag for ruv */ + CSNPL *min_csn_pl; /* Pending list for minimal CSN */ + void *csn_pl_reg_id; /* registration assignment for csn callbacks */ + unsigned long repl_state_flags; /* state flags */ + PRUint32 repl_flags; /* persistent, externally visible flags */ + PRLock *repl_lock; /* protects entire structure */ + Slapi_Eq_Context repl_eqcxt_rs; /* context to cancel event that saves ruv */ + Slapi_Eq_Context repl_eqcxt_tr; /* context to cancel event that reaps tombstones */ + Object *repl_csngen; /* CSN generator for this replica */ + PRBool repl_csn_assigned; /* Flag set when new csn is assigned. */ + PRUint32 repl_purge_delay; /* When purgeable, CSNs are held on to for this many extra seconds */ + PRBool tombstone_reap_stop; /* TRUE when the tombstone reaper should stop */ + PRBool tombstone_reap_active; /* TRUE when the tombstone reaper is running */ + long tombstone_reap_interval; /* Time in seconds between tombstone reaping */ + Slapi_ValueSet *repl_referral; /* A list of administrator provided referral URLs */ + PRBool state_update_inprogress; /* replica state is being updated */ + PRLock *agmt_lock; /* protects agreement creation, start and stop */ + char *locking_purl; /* supplier who has exclusive access */ +}; + + +typedef struct reap_callback_data +{ + int rc; + unsigned long num_entries; + unsigned long num_purged_entries; + CSN *purge_csn; + PRBool *tombstone_reap_stop; +} reap_callback_data; + + +/* Forward declarations of helper functions*/ +static Slapi_Entry* _replica_get_config_entry (const Slapi_DN *root); +static int _replica_check_validity (const Replica *r); +static int _replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext); +static int _replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext); +static int _replica_configure_ruv (Replica *r, PRBool isLocked); +static void _replica_update_state (time_t when, void *arg); +static char * _replica_get_config_dn (const Slapi_DN *root); +static char * _replica_type_as_string (const Replica *r); +static int replica_create_ruv_tombstone(Replica *r); +static void assign_csn_callback(const CSN *csn, void *data); +static void abort_csn_callback(const CSN *csn, void *data); +static void eq_cb_reap_tombstones(time_t when, void *arg); +static CSN *_replica_get_purge_csn_nolock (const Replica *r); +static void replica_get_referrals_nolock (const Replica *r, char ***referrals); +static void replica_clear_legacy_referrals (const Slapi_DN *repl_root_sdn, char **referrals, const char *state); +static void replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr); +static int replica_log_ruv_elements_nolock (const Replica *r); +static void replica_replace_ruv_tombstone(Replica *r); +static void start_agreements_for_replica (Replica *r, PRBool start); + +/* Allocates new replica and reads its state and state of its component from + * various parts of the DIT. + */ +Replica * +replica_new(const Slapi_DN *root) +{ + Replica *r = NULL; + Slapi_Entry *e = NULL; + char errorbuf[BUFSIZ]; + char ebuf[BUFSIZ]; + + PR_ASSERT (root); + + /* check if there is a replica associated with the tree */ + e = _replica_get_config_entry (root); + if (e) + { + errorbuf[0] = '\0'; + r = replica_new_from_entry(e, errorbuf, + PR_FALSE /* not a newly added entry */); + + if (NULL == r) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Unable to " + "configure replica %s: %s\n", + escape_string(slapi_sdn_get_dn(root), ebuf), + errorbuf); + } + + slapi_entry_free (e); + } + + return r; +} + +/* constructs the replica object from the newly added entry */ +Replica * +replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation) +{ + int rc = 0; + Replica *r; + RUV *ruv; + char *repl_name = NULL; + + if (e == NULL) + { + if (NULL != errortext) + { + sprintf (errortext, "NULL entry"); + } + return NULL; + } + + r = (Replica *)slapi_ch_calloc(1, sizeof(Replica)); + + if ((r->repl_lock = PR_NewLock()) == NULL) + { + if (NULL != errortext) + { + sprintf (errortext, "failed to create replica lock"); + } + rc = -1; + goto done; + } + + if ((r->agmt_lock = PR_NewLock()) == NULL) + { + if (NULL != errortext) + { + sprintf (errortext, "failed to create replica lock"); + } + rc = -1; + goto done; + } + + /* read parameters from the replica config entry */ + rc = _replica_init_from_config (r, e, errortext); + if (rc != 0) + { + goto done; + } + + /* configure ruv */ + rc = _replica_configure_ruv (r, PR_FALSE); + if (rc != 0) + { + goto done; + } + + /* If smallest csn exists in RUV for our local replica, it's ok to begin iteration */ + ruv = (RUV*) object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + + if (is_add_operation) + { + /* + * This is called by an ldap add operation. + * Update the entry to contain information generated + * during replica initialization + */ + rc = _replica_update_entry (r, e, errortext); + } + else + { + /* + * Entry is already in dse.ldif - update it on the disk + * (done by the update state event scheduled below) + */ + } + if (rc != 0) + goto done; + + /* ONREPL - the state update can occur before the entry is added to the DIT. + In that case the updated would fail but nothing bad would happen. The next + scheduled update would save the state */ + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_rs = slapi_eq_repeat(_replica_update_state, repl_name, + current_time () + START_UPDATE_DELAY, RUV_SAVE_INTERVAL); + + if (r->tombstone_reap_interval > 0) + { + /* + * Reap Tombstone should be started some time after the plugin started. + * This will allow the server to fully start before consuming resources. + */ + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_tr = slapi_eq_repeat(eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval); + } + + if (r->legacy_consumer) + { + char ebuf[BUFSIZ]; + + legacy_consumer_init_referrals (r); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_new_from_entry: " + "replica for %s was configured as legacy consumer\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + +done: + if (rc != 0 && r) + { + replica_destroy ((void**)&r); + } + + return r; +} + + +void +replica_flush(Replica *r) +{ + PR_ASSERT(NULL != r); + if (NULL != r) + { + PR_Lock(r->repl_lock); + /* Make sure we dump the CSNGen state */ + r->repl_csn_assigned = PR_TRUE; + PR_Unlock(r->repl_lock); + /* This function take the Lock Inside */ + /* And also write the RUV */ + _replica_update_state((time_t)0, r->repl_name); + } +} + + +/* + * Deallocate a replica. arg should point to the address of a + * pointer that points to a replica structure. + */ +void +replica_destroy(void **arg) +{ + Replica *r; + void *repl_name; + + if (arg == NULL) + return; + + r = *((Replica **)arg); + + PR_ASSERT(r); + + slapi_log_error (SLAPI_LOG_REPL, NULL, "replica_destroy\n"); + + /* + * The function will not be called unless the refcnt of its + * wrapper object is 0. Hopefully this refcnt could sync up + * this destruction and the events such as tombstone reap + * and ruv updates. + */ + + if (r->repl_eqcxt_rs) + { + repl_name = slapi_eq_get_arg (r->repl_eqcxt_rs); + slapi_ch_free (&repl_name); + slapi_eq_cancel(r->repl_eqcxt_rs); + r->repl_eqcxt_rs = NULL; + } + + if (r->repl_eqcxt_tr) + { + repl_name = slapi_eq_get_arg (r->repl_eqcxt_tr); + slapi_ch_free (&repl_name); + slapi_eq_cancel(r->repl_eqcxt_tr); + r->repl_eqcxt_tr = NULL; + } + + if (r->repl_root) + { + slapi_sdn_free(&r->repl_root); + } + + slapi_ch_free_string(&r->locking_purl); + + if (r->updatedn_list) + { + replica_updatedn_list_free(r->updatedn_list); + r->updatedn_list = NULL; + } + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&r->repl_name); + slapi_ch_free ((void**)&r->legacy_purl); + + if (r->repl_lock) + { + PR_DestroyLock(r->repl_lock); + r->repl_lock = NULL; + } + + if (r->agmt_lock) + { + PR_DestroyLock(r->agmt_lock); + r->agmt_lock = NULL; + } + + if(NULL != r->repl_ruv) + { + object_release(r->repl_ruv); + } + + if(NULL != r->repl_csngen) + { + if (r->csn_pl_reg_id) + { + csngen_unregister_callbacks((CSNGen *)object_get_data (r->repl_csngen), r->csn_pl_reg_id); + } + object_release(r->repl_csngen); + } + + if (NULL != r->repl_referral) + { + slapi_valueset_free(r->repl_referral); + } + + if (NULL != r->min_csn_pl) + { + csnplFree(&r->min_csn_pl);; + } + + slapi_ch_free((void **)arg); +} + +/* + * Attempt to obtain exclusive access to replica (advisory only) + * + * Returns PR_TRUE if exclusive access was granted, + * PR_FALSE otherwise + * The parameter isInc tells whether or not the replica is being + * locked for an incremental update session - if the replica is + * successfully locked, this value is used - if the replica is already + * in use, this value will be set to TRUE or FALSE, depending on what + * type of update session has the replica in use currently + * locking_purl is the supplier who is attempting to acquire access + * current_purl is the supplier who already has access, if any + */ +PRBool +replica_get_exclusive_access(Replica *r, PRBool *isInc, int connid, int opid, + const char *locking_purl, + char **current_purl) +{ + char ebuf[BUFSIZ]; + PRBool rval = PR_TRUE; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + if (r->repl_state_flags & REPLICA_IN_USE) + { + if (isInc) + *isInc = (r->repl_state_flags & REPLICA_INCREMENTAL_IN_PROGRESS); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Replica in use locking_purl=%s\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), + r->locking_purl ? r->locking_purl : "unknown"); + rval = PR_FALSE; + if (current_purl) + { + *current_purl = slapi_ch_strdup(r->locking_purl); + } + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": Acquired replica\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + r->repl_state_flags |= REPLICA_IN_USE; + if (isInc && *isInc) + { + r->repl_state_flags |= REPLICA_INCREMENTAL_IN_PROGRESS; + } + else + { + /* if connid or opid != 0, it's a total update */ + /* Both set to 0 means we're disabling replication */ + if (connid || opid) + { + r->repl_state_flags |= REPLICA_TOTAL_IN_PROGRESS; + } + } + slapi_ch_free_string(&r->locking_purl); + r->locking_purl = slapi_ch_strdup(locking_purl); + } + PR_Unlock(r->repl_lock); + return rval; +} + +/* + * Relinquish exclusive access to the replica + */ +void +replica_relinquish_exclusive_access(Replica *r, int connid, int opid) +{ + char ebuf[BUFSIZ]; + PRBool isInc; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + isInc = (r->repl_state_flags & REPLICA_INCREMENTAL_IN_PROGRESS); + /* check to see if the replica is in use and log a warning if not */ + if (!(r->repl_state_flags & REPLICA_IN_USE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Replica not in use\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } else { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Released replica\n", + connid, opid, + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + slapi_ch_free_string(&r->locking_purl); + r->repl_state_flags &= ~(REPLICA_IN_USE); + if (isInc) + r->repl_state_flags &= ~(REPLICA_INCREMENTAL_IN_PROGRESS); + else + r->repl_state_flags &= ~(REPLICA_TOTAL_IN_PROGRESS); + } + PR_Unlock(r->repl_lock); +} + +/* + * Returns root of the replicated area + */ +PRBool +replica_get_tombstone_reap_active(const Replica *r) +{ + PR_ASSERT(r); + + return(r->tombstone_reap_active); +} + +/* + * Returns root of the replicated area + */ +const Slapi_DN * +replica_get_root(const Replica *r) /* ONREPL - should we return copy instead? */ +{ + PR_ASSERT(r); + + /* replica root never changes so we don't have to lock */ + return(r->repl_root); +} + +/* + * Returns normalized dn of the root of the replicated area + */ +const char * +replica_get_name(const Replica *r) /* ONREPL - should we return copy instead? */ +{ + PR_ASSERT(r); + + /* replica name never changes so we don't have to lock */ + return(r->repl_name); +} + +/* + * Returns replicaid of this replica + */ +ReplicaId +replica_get_rid (const Replica *r) +{ + ReplicaId rid; + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + rid = r->repl_rid; + PR_Unlock(r->repl_lock); + return rid; +} + +/* + * Sets replicaid of this replica - should only be used when also changing the type + */ +void +replica_set_rid (Replica *r, ReplicaId rid) +{ + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + r->repl_rid = rid; + PR_Unlock(r->repl_lock); +} + +/* Returns true if replica was initialized through ORC or import; + * otherwise, false. An uninitialized replica should return + * LDAP_UNWILLING_TO_PERFORM to all client requests + */ +PRBool +replica_is_initialized (const Replica *r) +{ + PR_ASSERT(r); + return (r->repl_ruv != NULL); +} + +/* + * Returns refcounted object that contains RUV. The caller should release the + * object once it is no longer used. To release, call object_release + */ +Object * +replica_get_ruv (const Replica *r) +{ + Object *ruv = NULL; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + PR_ASSERT (r->repl_ruv); + + object_acquire (r->repl_ruv); + + ruv = r->repl_ruv; + + PR_Unlock(r->repl_lock); + + return ruv; +} + +/* + * Sets RUV vector. This function should be called during replica + * (re)initialization. During normal operation, the RUV is read from + * the root of the replicated in the replica_new call + */ +void +replica_set_ruv (Replica *r, RUV *ruv) +{ + PR_ASSERT(r && ruv); + + PR_Lock(r->repl_lock); + + if(NULL != r->repl_ruv) + { + object_release(r->repl_ruv); + } + + /* if the local replica is not in the RUV and it is writable - add it + and reinitialize min_csn pending list */ + if (r->repl_type == REPLICA_TYPE_UPDATABLE) + { + CSN *csn = NULL; + if (r->min_csn_pl) + csnplFree (&r->min_csn_pl); + + if (ruv_contains_replica (ruv, r->repl_rid)) + { + ruv_get_smallest_csn_for_replica(ruv, r->repl_rid, &csn); + if (csn) + csn_free (&csn); + else + r->min_csn_pl = csnplNew (); + /* We need to make sure the local ruv element is the 1st. */ + ruv_move_local_supplier_to_first(ruv, r->repl_rid); + } + else + { + r->min_csn_pl = csnplNew (); + /* To be sure that the local is in first */ + ruv_add_index_replica(ruv, r->repl_rid, multimaster_get_local_purl(), 1); + } + } + + r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy); + r->repl_ruv_dirty = PR_TRUE; + + PR_Unlock(r->repl_lock); +} + +/* + * Update one particular CSN in an RUV. This is meant to be called + * whenever (a) the server has processed a client operation and + * needs to update its CSN, or (b) the server is completing an + * inbound replication session operation, and needs to update its + * local RUV. + */ +void +replica_update_ruv(Replica *r, const CSN *updated_csn, const char *replica_purl) +{ + char csn_str[CSN_STRSIZE]; + char ebuf[BUFSIZ]; + + PR_ASSERT(NULL != r); + PR_ASSERT(NULL != updated_csn); +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "replica_update_ruv: csn %s\n", + csn_as_string(updated_csn, PR_FALSE, csn_str)); /* XXXggood remove debugging */ +#endif + if (NULL == r) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: replica " + "is NULL\n"); + } + else if (NULL == updated_csn) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: csn " + "is NULL when updating replica %s\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + else + { + RUV *ruv; + PR_Lock(r->repl_lock); + + if (r->repl_ruv != NULL) + { + ruv = object_get_data(r->repl_ruv); + if (NULL != ruv) + { + ReplicaId rid = csn_get_replicaid(updated_csn); + if (rid == r->repl_rid) + { + if (NULL != r->min_csn_pl) + { + CSN *min_csn; + PRBool committed; + (void)csnplCommit(r->min_csn_pl, updated_csn); + min_csn = csnplGetMinCSN(r->min_csn_pl, &committed); + if (NULL != min_csn) + { + if (committed) + { + ruv_set_min_csn(ruv, min_csn, replica_purl); + csnplFree(&r->min_csn_pl); + } + csn_free(&min_csn); + } + } + } + /* Update max csn for local and remote replicas */ + if (ruv_update_ruv (ruv, updated_csn, replica_purl, rid == r->repl_rid) + != RUV_SUCCESS) + { + slapi_log_error(SLAPI_LOG_FATAL, + repl_plugin_name, "replica_update_ruv: unable " + "to update RUV for replica %s, csn = %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), + csn_as_string(updated_csn, PR_FALSE, csn_str)); + } + + r->repl_ruv_dirty = PR_TRUE; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_update_ruv: unable to get RUV object for replica " + "%s\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_update_ruv: " + "unable to initialize RUV for replica %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + } + PR_Unlock(r->repl_lock); + } +} + +/* + * Returns refcounted object that contains csn generator. The caller should release the + * object once it is no longer used. To release, call object_release + */ +Object * +replica_get_csngen (const Replica *r) +{ + Object *csngen; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + object_acquire (r->repl_csngen); + csngen = r->repl_csngen; + + PR_Unlock(r->repl_lock); + + return csngen; +} + +/* + * Returns the replica type. + */ +ReplicaType +replica_get_type (const Replica *r) +{ + PR_ASSERT(r); + return r->repl_type; +} + +/* + * Sets the replica type. + */ +void +replica_set_type (Replica *r, ReplicaType type) +{ + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + r->repl_type = type; + PR_Unlock(r->repl_lock); +} + +/* + * Returns PR_TRUE if this replica is a consumer of 4.0 server + * and PR_FALSE otherwise + */ +PRBool +replica_is_legacy_consumer (const Replica *r) +{ + PR_ASSERT(r); + return r->legacy_consumer; +} + +/* + * Sets the replica type. + */ +void +replica_set_legacy_consumer (Replica *r, PRBool legacy_consumer) +{ + int legacy2mmr; + Slapi_DN *repl_root_sdn = NULL; + char **referrals = NULL; + char *replstate = NULL; + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + legacy2mmr = r->legacy_consumer && !legacy_consumer; + + /* making the server a regular 5.0 replica */ + if (legacy2mmr) + { + slapi_ch_free ((void**)&r->legacy_purl); + /* Remove copiedFrom/copyingFrom attributes from the root entry */ + /* set the right state in the mapping tree */ + if (r->repl_type == REPLICA_TYPE_READONLY) + { + replica_get_referrals_nolock (r, &referrals); + replstate = STATE_UPDATE_REFERRAL; + } + else /* updateable */ + { + replstate = STATE_BACKEND; + } + } + + r->legacy_consumer = legacy_consumer; + repl_root_sdn = slapi_sdn_dup(r->repl_root); + PR_Unlock(r->repl_lock); + + if (legacy2mmr) + { + replica_clear_legacy_referrals(repl_root_sdn, referrals, replstate); + /* Also change state of the mapping tree node and/or referrals */ + replica_remove_legacy_attr (repl_root_sdn, type_copiedFrom); + replica_remove_legacy_attr (repl_root_sdn, type_copyingFrom); + } + charray_free(referrals); + slapi_sdn_free(&repl_root_sdn); +} + +/* Gets partial url of the legacy supplier - applicable for legacy consumer only */ +char * +replica_get_legacy_purl (const Replica *r) +{ + char *purl; + + PR_Lock (r->repl_lock); + + PR_ASSERT (r->legacy_consumer); + + purl = slapi_ch_strdup (r->legacy_purl); + + PR_Unlock (r->repl_lock); + + return purl; +} + +void +replica_set_legacy_purl (Replica *r, const char *purl) +{ + PR_Lock (r->repl_lock); + + PR_ASSERT (r->legacy_consumer); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&r->legacy_purl); + + r->legacy_purl = slapi_ch_strdup (purl); + + PR_Unlock (r->repl_lock); +} + +/* + * Returns true if sdn is the same as updatedn and false otherwise + */ +PRBool +replica_is_updatedn (const Replica *r, const Slapi_DN *sdn) +{ + PRBool result; + + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + if (sdn == NULL) + { + result = (r->updatedn_list == NULL); + } + else if (r->updatedn_list == NULL) + { + result = PR_FALSE; + } + else + { + result = replica_updatedn_list_ismember(r->updatedn_list, sdn); + } + + PR_Unlock(r->repl_lock); + + return result; +} + +/* + * Sets updatedn list for this replica + */ +void +replica_set_updatedn (Replica *r, const Slapi_ValueSet *vs, int mod_op) +{ + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + if (!r->updatedn_list) + r->updatedn_list = replica_updatedn_list_new(NULL); + + if (mod_op & LDAP_MOD_DELETE || vs == NULL || + (0 == slapi_valueset_count(vs))) /* null value also causes list deletion */ + replica_updatedn_list_delete(r->updatedn_list, vs); + else if (mod_op & LDAP_MOD_REPLACE) + replica_updatedn_list_replace(r->updatedn_list, vs); + else if (mod_op & LDAP_MOD_ADD) + replica_updatedn_list_add(r->updatedn_list, vs); + + PR_Unlock(r->repl_lock); +} + +/* gets current replica generation for this replica */ +char *replica_get_generation (const Replica *r) +{ + int rc = 0; + char *gen = NULL; + + if (r) + { + PR_Lock(r->repl_lock); + + PR_ASSERT (r->repl_ruv); + + if (rc == 0) + gen = ruv_get_replica_generation ((RUV*)object_get_data (r->repl_ruv)); + + PR_Unlock(r->repl_lock); + } + + return gen; +} + +PRBool replica_is_flag_set (const Replica *r, PRUint32 flag) +{ + if (r) + return (r->repl_flags & flag); + else + return PR_FALSE; +} + +void replica_set_flag (Replica *r, PRUint32 flag, PRBool clear) +{ + if (r == NULL) + return; + + PR_Lock(r->repl_lock); + + if (clear) + { + r->repl_flags &= ~flag; + } + else + { + r->repl_flags |= flag; + } + + PR_Unlock(r->repl_lock); +} + +void replica_replace_flags (Replica *r, PRUint32 flags) +{ + if (r) + { + PR_Lock(r->repl_lock); + r->repl_flags = flags; + PR_Unlock(r->repl_lock); + } +} + +void +replica_get_referrals(const Replica *r, char ***referrals) +{ + PR_Lock(r->repl_lock); + replica_get_referrals_nolock (r, referrals); + PR_Unlock(r->repl_lock); +} + +void +replica_set_referrals(Replica *r,const Slapi_ValueSet *vs) +{ + int ii = 0; + Slapi_Value *vv = NULL; + if (r->repl_referral == NULL) + { + r->repl_referral = slapi_valueset_new(); + } + else + { + slapi_valueset_done(r->repl_referral); + } + slapi_valueset_set_valueset(r->repl_referral, vs); + /* make sure the DN is included in the referral LDAP URL */ + if (r->repl_referral) + { + Slapi_ValueSet *newvs = slapi_valueset_new(); + const char *repl_root = slapi_sdn_get_dn(r->repl_root); + int rootlen = strlen(repl_root); + ii = slapi_valueset_first_value(r->repl_referral, &vv); + while (vv) + { + const char *ref = slapi_value_get_string(vv); + struct ldap_url_desc *lud = NULL; + int myrc = ldap_url_parse(ref, &lud); + /* see if the dn is already in the referral URL */ + if (myrc == LDAP_URL_ERR_NODN || !lud || !lud->lud_dn) { + /* add the dn */ + Slapi_Value *newval = NULL; + int len = strlen(ref); + char *tmpref = NULL; + int need_slash = 0; + if (ref[len-1] != '/') { + len++; /* add another one for the slash */ + need_slash = 1; + } + len += rootlen + 2; + tmpref = slapi_ch_malloc(len); + sprintf(tmpref, "%s%s%s", ref, (need_slash ? "/" : ""), + repl_root); + newval = slapi_value_new_string(tmpref); + slapi_ch_free_string(&tmpref); /* sv_new_string makes a copy */ + slapi_valueset_add_value(newvs, newval); + slapi_value_free(&newval); /* s_vs_add_value makes a copy */ + } + if (lud) + ldap_free_urldesc(lud); + ii = slapi_valueset_next_value(r->repl_referral, ii, &vv); + } + if (slapi_valueset_count(newvs) > 0) { + slapi_valueset_done(r->repl_referral); + slapi_valueset_set_valueset(r->repl_referral, newvs); + } + slapi_valueset_free(newvs); /* s_vs_set_vs makes a copy */ + } +} + +int +replica_update_csngen_state (Replica *r, const RUV *ruv) +{ + int rc = 0; + CSNGen *gen; + CSN *csn = NULL; + + PR_ASSERT (r && ruv); + + rc = ruv_get_max_csn(ruv, &csn); + if (rc != RUV_SUCCESS) + { + return -1; + } + + if (csn == NULL) /* ruv contains no csn - we are done */ + { + return 0; + } + + PR_Lock(r->repl_lock); + + gen = (CSNGen *)object_get_data (r->repl_csngen); + PR_ASSERT (gen); + + rc = csngen_adjust_time (gen, csn); + if (rc != CSN_SUCCESS) + { + rc = -1; + goto done; + } + + rc = 0; + +done: + + PR_Unlock(r->repl_lock); + if (csn) + csn_free (&csn); + + return rc; +} + +/* + * dumps replica state for debugging purpose + */ +void +replica_dump(Replica *r) +{ + char *updatedn_list = NULL; + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "Replica state:\n"); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica root: %s\n", + slapi_sdn_get_ndn (r->repl_root)); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica type: %s\n", + _replica_type_as_string (r)); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\treplica id: %d\n", r->repl_rid); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tflags: %d\n", r->repl_flags); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tstate flags: %d\n", r->repl_state_flags); + if (r->updatedn_list) + updatedn_list = replica_updatedn_list_to_string(r->updatedn_list, "\n\t\t"); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tupdate dn: %s\n", + updatedn_list? updatedn_list : "not configured"); + slapi_ch_free_string(&updatedn_list); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\truv: %s configured and is %sdirty\n", + r->repl_ruv ? "" : "not", r->repl_ruv_dirty ? "" : "not "); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "\tCSN generator: %s configured\n", + r->repl_csngen ? "" : "not"); + /* JCMREPL - Dump Referrals */ + + PR_Unlock(r->repl_lock); +} + + +/* + * Return the CSN of the purge point. Any CSNs smaller than the + * purge point can be safely removed from entries within this + * this replica. Returns an allocated CSN that must be freed by + * the caller, or NULL if purging is disabled. + */ + +CSN * +replica_get_purge_csn(const Replica *r) +{ + CSN *csn; + + PR_Lock(r->repl_lock); + + csn= _replica_get_purge_csn_nolock(r); + + PR_Unlock(r->repl_lock); + + return csn; +} + + +/* + * This function logs a dummy entry for the smallest csn in the RUV. + * This is necessary because, to get the next change, we need to position + * changelog on the previous change. So this function insures that we always have one. + */ + +/* ONREPL we will need to change this function to log all the + * ruv elements not just the smallest when changelog iteration + * algoritm changes to iterate replica by replica +*/ +int +replica_log_ruv_elements (const Replica *r) +{ + int rc = 0; + + PR_ASSERT (r); + + PR_Lock(r->repl_lock); + + rc = replica_log_ruv_elements_nolock (r); + + PR_Unlock(r->repl_lock); + + return rc; +} + +void +consumer5_set_mapping_tree_state_for_replica(const Replica *r, RUV *supplierRuv) +{ + const Slapi_DN *repl_root_sdn= replica_get_root(r); + char **ruv_referrals= NULL; + char **replica_referrals= NULL; + RUV *ruv; + int state_backend = -1; + const char *mtn_state = NULL; + + PR_Lock (r->repl_lock); + + if ( supplierRuv == NULL ) + { + ruv = (RUV*)object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + + ruv_referrals= ruv_get_referrals(ruv); /* ruv_referrals has to be free'd */ + } + else + { + ruv_referrals = ruv_get_referrals(supplierRuv); + } + + replica_get_referrals_nolock (r, &replica_referrals); /* replica_referrals has to be free'd */ + + /* JCMREPL - What if there's a Total update in progress? */ + if( (r->repl_type==REPLICA_TYPE_READONLY) || (r->legacy_consumer) ) + { + state_backend = 0; + } + else if (r->repl_type==REPLICA_TYPE_UPDATABLE) + { + state_backend = 1; + } + /* Unlock to avoid changing MTN state under repl lock */ + PR_Unlock (r->repl_lock); + + if(state_backend == 0 ) + { + /* Read-Only - The mapping tree should be refering all update operations. */ + mtn_state = STATE_UPDATE_REFERRAL; + } + else if (state_backend == 1) + { + /* Updatable - The mapping tree should be accepting all update operations. */ + mtn_state = STATE_BACKEND; + } + + /* JCMREPL - Check the return code. */ + repl_set_mtn_state_and_referrals(repl_root_sdn, mtn_state, NULL, + ruv_referrals, replica_referrals); + charray_free(ruv_referrals); + charray_free(replica_referrals); +} + +void +replica_set_enabled (Replica *r, PRBool enable) +{ + char *repl_name = NULL; + + PR_ASSERT (r); + + PR_Lock (r->repl_lock); + + if (enable) + { + if (r->repl_eqcxt_rs == NULL) /* event is not already registered */ + { + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_rs = slapi_eq_repeat(_replica_update_state, repl_name, + current_time() + START_UPDATE_DELAY, RUV_SAVE_INTERVAL); + } + } + else /* disable */ + { + if (r->repl_eqcxt_rs) /* event is still registerd */ + { + repl_name = slapi_eq_get_arg (r->repl_eqcxt_rs); + slapi_ch_free ((void**)&repl_name); + slapi_eq_cancel(r->repl_eqcxt_rs); + r->repl_eqcxt_rs = NULL; + } + } + + PR_Unlock (r->repl_lock); +} + +/* This function is generally called when replica's data store + is reloaded. It retrieves new RUV from the datastore. If new + RUV does not exist or if it is not as up to date as the purge RUV + of the corresponding changelog file, we need to remove */ + +/* the function minimizes the use of replica lock where ever possible. + Locking replica lock while calling changelog functions + causes a deadlock because changelog calls replica functions that + that lock the same lock */ + +int +replica_reload_ruv (Replica *r) +{ + int rc = 0; + Object *old_ruv_obj = NULL, *new_ruv_obj = NULL; + RUV *upper_bound_ruv = NULL; + RUV *new_ruv = NULL; + Object *r_obj; + + PR_ASSERT (r); + + PR_Lock (r->repl_lock); + + old_ruv_obj = r->repl_ruv; + + r->repl_ruv = NULL; + + rc = _replica_configure_ruv (r, PR_TRUE); + + PR_Unlock (r->repl_lock); + + if (rc != 0) + { + return rc; + } + + /* check if there is a changelog and whether this replica logs changes */ + if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES) + { + + /* Compare new ruv to the changelog's upper bound ruv. We could only keep + the existing changelog if its upper bound is the same as replica's RUV. + This is because if changelog has changes not in RUV, they will be + eventually sent to the consumer's which will cause a state mismatch + (because the supplier does not actually contain the changes in its data store. + If, on the other hand, the changelog is not as up to date as the supplier, + it is not really useful since out of sync consumer's can't be brought + up to date using this changelog and hence will need to be reinitialized */ + + /* replace ruv to make sure we work with the correct changelog file */ + PR_Lock (r->repl_lock); + + new_ruv_obj = r->repl_ruv; + r->repl_ruv = old_ruv_obj; + + PR_Unlock (r->repl_lock); + + rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv); + if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) + { + return -1; + } + + if (upper_bound_ruv) + { + new_ruv = object_get_data (new_ruv_obj); + PR_ASSERT (new_ruv); + + /* ONREPL - there are more efficient ways to establish RUV equality. + However, because this is not in the critical path and we at most + have 2 elements in the RUV, this will not effect performance */ + + if (!ruv_covers_ruv (new_ruv, upper_bound_ruv) || + !ruv_covers_ruv (upper_bound_ruv, new_ruv)) + { + char ebuf[BUFSIZ]; + + /* create a temporary replica object to conform to the interface */ + r_obj = object_new (r, NULL); + + /* We can't use existing changelog - remove existing file */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_reload_ruv: " + "Warning: new data for replica %s does not match the data in the changelog.\n" + " Recreating the changelog file. This could affect replication with replica's " + " consumers in which case the consumers should be reinitialized.\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + rc = cl5DeleteDBSync (r_obj); + + /* reinstate new ruv */ + PR_Lock (r->repl_lock); + + r->repl_ruv = new_ruv_obj; + + object_release (r_obj); + + if (rc == CL5_SUCCESS) + { + /* log changes to mark starting point for replication */ + rc = replica_log_ruv_elements_nolock (r); + } + + PR_Unlock (r->repl_lock); + } + else + { + /* we just need to reinstate new ruv */ + PR_Lock (r->repl_lock); + + r->repl_ruv = new_ruv_obj; + + PR_Unlock (r->repl_lock); + } + } + else /* upper bound vector is not there - we have no changes logged */ + { + /* reinstate new ruv */ + PR_Lock (r->repl_lock); + + r->repl_ruv = new_ruv_obj; + + /* just log elements of the current RUV. This is to have + a starting point for iteration through the changes */ + rc = replica_log_ruv_elements_nolock (r); + + PR_Unlock (r->repl_lock); + } + } + + if (rc == 0) + { + consumer5_set_mapping_tree_state_for_replica(r, NULL); + /* reset mapping tree referrals based on new local RUV */ + } + + if (old_ruv_obj) + object_release (old_ruv_obj); + + if (upper_bound_ruv) + ruv_destroy (&upper_bound_ruv); + + return rc; +} + +/* this function is called during server startup for each replica + to check whether the replica's data was reloaded offline and + whether replica's changelog needs to be reinitialized */ + +/* the function does not use replica lock but all functions it calls are + thread safe. Locking replica lock while calling changelog functions + causes a deadlock because changelog calls replica functions that + that lock the same lock */ +int replica_check_for_data_reload (Replica *r, void *arg) +{ + int rc = 0; + RUV *upper_bound_ruv = NULL; + RUV *r_ruv = NULL; + Object *r_obj, *ruv_obj; + int cl_cover_be, be_cover_cl; + + PR_ASSERT (r); + + /* check that we have a changelog and if this replica logs changes */ + if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES) + { + /* Compare new ruv to the purge ruv. If the new contains csns which + are smaller than those in purge ruv, we need to remove old and + create new changelog file for this replica. This is because we + will not have sufficient changes to incrementally update a consumer + to the current state of the supplier. */ + + rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv); + if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND) + { + return -1; + } + + if (upper_bound_ruv) + { + ruv_obj = replica_get_ruv (r); + r_ruv = object_get_data (ruv_obj); + PR_ASSERT (r_ruv); + + /* Compare new ruv to the changelog's upper bound ruv. We could only keep + the existing changelog if its upper bound is the same as replica's RUV. + This is because if changelog has changes not in RUV, they will be + eventually sent to the consumer's which will cause a state mismatch + (because the supplier does not actually contain the changes in its data store. + If, on the other hand, the changelog is not as up to date as the supplier, + it is not really useful since out of sync consumer's can't be brought + up to date using this changelog and hence will need to be reinitialized */ + + /* + * Actually we can ignore the scenario that the changelog's upper + * bound ruv covers data store's ruv for two reasons: (1) a change + * is always written to the changelog after it is committed to the + * data store; (2) a change will be ignored if the server has seen + * it before - this happens frequently at the beginning of replication + * sessions. + */ + + be_cover_cl = ruv_covers_ruv (r_ruv, upper_bound_ruv); + cl_cover_be = ruv_covers_ruv (upper_bound_ruv, r_ruv); + if (!cl_cover_be) + { + /* the data was reloaded and we can no longer use existing changelog */ + char ebuf[BUFSIZ]; + + /* create a temporary replica object to conform to the interface */ + r_obj = object_new (r, NULL); + + /* We can't use existing changelog - remove existing file */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_check_for_data_reload: " + "Warning: data for replica %s was reloaded and it no longer matches the data " + "in the changelog (replica data %s changelog). Recreating the changelog file. This could affect replication " + "with replica's consumers in which case the consumers should be reinitialized.\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), + ((!be_cover_cl && !cl_cover_be) ? "<>" : (!be_cover_cl ? "<" : ">")) ); + + rc = cl5DeleteDBSync (r_obj); + + object_release (r_obj); + + if (rc == CL5_SUCCESS) + { + /* log changes to mark starting point for replication */ + rc = replica_log_ruv_elements (r); + } + } + + object_release (ruv_obj); + } + else /* we have no changes currently logged for this replica */ + { + /* log changes to mark starting point for replication */ + rc = replica_log_ruv_elements (r); + } + } + + if (rc == 0) + { + /* reset mapping tree referrals based on new local RUV */ + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + + if (upper_bound_ruv) + ruv_destroy (&upper_bound_ruv); + + return rc; +} + +/* Helper functions */ +/* reads replica configuration entry. The entry is the child of the + mapping tree node for the replica's backend */ + +static Slapi_Entry* +_replica_get_config_entry (const Slapi_DN *root) +{ + int rc = 0; + char *dn = NULL; + Slapi_Entry **entries; + Slapi_Entry *e = NULL; + Slapi_PBlock *pb = NULL; + + dn = _replica_get_config_dn (root); + pb = slapi_pblock_new (); + + slapi_search_internal_set_pb (pb, dn, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL, + NULL, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_search_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc == 0) + { + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + e = slapi_entry_dup (entries [0]); + } + + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + slapi_ch_free_string(&dn); + + return e; +} + +static int +_replica_check_validity (const Replica *r) +{ + PR_ASSERT (r); + + if (r->repl_root == NULL || r->repl_type == 0 || r->repl_rid == 0 || + r->repl_rid > MAX_REPLICA_ID || r->repl_csngen == NULL || r->repl_name == NULL) + { + return -1; + } + else + { + return 0; + } +} + +/* replica configuration entry has the following format: + dn: cn=replica,<mapping tree node dn> + objectclass: top + objectclass: nsds5Replica + objectclass: extensibleObject + nsds5ReplicaRoot: <root of the replica> + nsds5ReplicaId: <replica id> + nsds5ReplicaType: <type of the replica: primary, read-write or read-only> + nsState: <state of the csn generator> missing the first time replica is started + nsds5ReplicaBindDN: <supplier update dn> consumers only + nsds5ReplicaReferral: <referral URL to updatable replica> consumers only + nsds5ReplicaPurgeDelay: <time, in seconds, to keep purgeable CSNs, 0 == keep forever> + nsds5ReplicaTombstonePurgeInterval: <time, in seconds, between tombstone purge runs, 0 == don't reap> + nsds5ReplicaLegacyConsumer: <TRUE | FALSE> + + richm: changed slapi entry from const to editable - if the replica id is supplied for a read + only replica, we ignore it and replace the value with the READ_ONLY_REPLICA_ID + */ +static int +_replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext) +{ + int rc; + Slapi_Attr *attr; + char *val; + CSNGen *gen; + char buf [BUFSIZ]; + char *errormsg = errortext? errortext : buf; + Slapi_Attr *a = NULL; + char dnescape[BUFSIZ]; /* for escape_string */ + + PR_ASSERT (r && e); + + /* get replica root */ + val = slapi_entry_attr_get_charptr (e, attr_replicaRoot); + if (val == NULL) + { + sprintf (errormsg, "failed to retrieve %s attribute from (%s)\n", + attr_replicaRoot, + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_init_from_config: %s\n", + errormsg); + + return -1; + } + + r->repl_root = slapi_sdn_new_dn_passin (val); + + /* get replica type */ + val = slapi_entry_attr_get_charptr (e, attr_replicaType); + if (val) + { + r->repl_type = atoi(val); + slapi_ch_free ((void**)&val); + } + else + { + r->repl_type = REPLICA_TYPE_READONLY; + } + + /* get legacy consumer flag */ + val = slapi_entry_attr_get_charptr (e, type_replicaLegacyConsumer); + if (val) + { + if (strcasecmp (val, "on") == 0 || strcasecmp (val, "yes") == 0 || + strcasecmp (val, "true") == 0 || strcasecmp (val, "1") == 0) + { + r->legacy_consumer = PR_TRUE; + } + else + { + r->legacy_consumer = PR_FALSE; + } + + slapi_ch_free ((void**)&val); + } + else + { + r->legacy_consumer = PR_FALSE; + } + + /* get replica flags */ + r->repl_flags = slapi_entry_attr_get_ulong(e, attr_flags); + + /* get replicaid */ + /* the replica id is ignored for read only replicas and is set to the + special value READ_ONLY_REPLICA_ID */ + if (r->repl_type == REPLICA_TYPE_READONLY) + { + r->repl_rid = READ_ONLY_REPLICA_ID; + slapi_entry_attr_set_uint(e, attr_replicaId, (unsigned int)READ_ONLY_REPLICA_ID); + } + /* a replica id is required for updatable and primary replicas */ + else if (r->repl_type == REPLICA_TYPE_UPDATABLE || + r->repl_type == REPLICA_TYPE_PRIMARY) + { + if ((val = slapi_entry_attr_get_charptr (e, attr_replicaId))) + { + int temprid = atoi (val); + slapi_ch_free ((void**)&val); + if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID) + { + sprintf (errormsg, + "attribute %s must have a value greater than 0 " + "and less than %d: entry %s", + attr_replicaId, READ_ONLY_REPLICA_ID, + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), + dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + else + { + r->repl_rid = (ReplicaId)temprid; + } + } + else + { + sprintf (errormsg, "failed to retrieve required %s attribute from %s", + attr_replicaId, + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), + dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + } + + attr = NULL; + rc = slapi_entry_attr_find(e, attr_state, &attr); + gen = csngen_new (r->repl_rid, attr); + if (gen == NULL) + { + sprintf (errormsg, "failed to create csn generator for replica (%s)", + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), + dnescape)); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + r->repl_csngen = object_new((void*)gen, (FNFree)csngen_free); + + /* Hook generator so we can maintain min/max CSN info */ + r->csn_pl_reg_id = csngen_register_callbacks(gen, assign_csn_callback, r, abort_csn_callback, r); + + /* get replication bind dn */ + r->updatedn_list = replica_updatedn_list_new(e); + + /* get replica name */ + val = slapi_entry_attr_get_charptr (e, attr_replicaName); + if (val) { + r->repl_name = val; + } + else + { + rc = slapi_uniqueIDGenerateString (&r->repl_name); + if (rc != UID_SUCCESS) + { + sprintf (errormsg, "failed to assign replica name for replica (%s); " + "uuid generator error - %d ", + escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape), + rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_init_from_config: %s\n", + errormsg); + return -1; + } + else + r->new_name = PR_TRUE; + } + + /* get the list of referrals */ + slapi_entry_attr_find( e, attr_replicaReferral, &attr ); + if(attr!=NULL) + { + slapi_attr_get_valueset(attr, &r->repl_referral); + } + + /* + * Set the purge offset (default 7 days). This is the extra + * time we allow purgeable CSNs to stick around, in case a + * replica regresses. Could also be useful when LCUP happens, + * since we don't know about LCUP replicas, and they can just + * turn up whenever they want to. + */ + if (slapi_entry_attr_find(e, type_replicaPurgeDelay, &a) == -1) + { + /* No purge delay provided, so use default */ + r->repl_purge_delay = 60 * 60 * 24 * 7; /* One week, in seconds */ + } + else + { + r->repl_purge_delay = slapi_entry_attr_get_uint(e, type_replicaPurgeDelay); + } + + if (slapi_entry_attr_find(e, type_replicaTombstonePurgeInterval, &a) == -1) + { + /* No reap interval provided, so use default */ + r->tombstone_reap_interval = 3600 * 24; /* One day */ + } + else + { + r->tombstone_reap_interval = slapi_entry_attr_get_int(e, type_replicaTombstonePurgeInterval); + } + + r->tombstone_reap_stop = r->tombstone_reap_active = PR_FALSE; + + return (_replica_check_validity (r)); +} + +/* This function updates the entry to contain information generated + during replica initialization. + Returns 0 if successful and -1 otherwise */ +static int +_replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext) +{ + int rc; + Slapi_Mod smod; + Slapi_Value *val; + + PR_ASSERT (r); + + /* add attribute that stores state of csn generator */ + rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod); + if (rc != CSN_SUCCESS) + { + sprintf (errortext, "failed to get csn generator's state; csn error - %d", rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_update_entry: %s\n", errortext); + return -1; + } + + val = slapi_value_new_berval(slapi_mod_get_first_value(&smod)); + + rc = slapi_entry_add_value (e, slapi_mod_get_type (&smod), val); + + slapi_value_free(&val); + slapi_mod_done (&smod); + + if (rc != 0) + { + sprintf (errortext, "failed to update replica entry"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_update_entry: %s\n", errortext); + return -1; + } + + /* add attribute that stores replica name */ + rc = slapi_entry_add_string (e, attr_replicaName, r->repl_name); + if (rc != 0) + { + sprintf (errortext, "failed to update replica entry"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_update_entry: %s\n", errortext); + return -1; + } + else + r->new_name = PR_FALSE; + + return 0; +} + +/* DN format: cn=replica,cn=\"<root>\",cn=mapping tree,cn=config */ +static char* +_replica_get_config_dn (const Slapi_DN *root) +{ + char *dn; + const char *mp_base = slapi_get_mapping_tree_config_root (); + int len; + + PR_ASSERT (root); + + len = strlen (REPLICA_RDN) + strlen (slapi_sdn_get_dn (root)) + + strlen (mp_base) + 8; /* 8 = , + cn= + \" + \" + , + \0 */ + + dn = (char*)slapi_ch_malloc (len); + sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, slapi_sdn_get_dn (root), mp_base); + + return dn; +} + +/* This function retrieves RUV from the root of the replicated tree. + * The attribute can be missing if + * (1) this replica is the first supplier and replica generation has not been assigned + * or + * (2) this is a consumer that has not been yet initialized + * In either case, replica_set_ruv should be used to further initialize the replica. + * Returns 0 on success, -1 on failure. If 0 is returned, the RUV is present in the replica. + */ +static int +_replica_configure_ruv (Replica *r, PRBool isLocked) +{ + Slapi_PBlock *pb = NULL; + char *attrs[2]; + int rc; + int return_value = -1; + Slapi_Entry **entries = NULL; + Slapi_Attr *attr; + RUV *ruv = NULL; + CSN *csn = NULL; + ReplicaId rid = 0; + char ebuf[BUFSIZ]; + + /* read ruv state from the ruv tombstone entry */ + pb = slapi_pblock_new(); + attrs[0] = (char*)type_ruvElement; + attrs[1] = NULL; + slapi_search_internal_set_pb( + pb, + slapi_sdn_get_dn(r->repl_root), + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, /* attrsonly */ + NULL, /* controls */ + RUV_STORAGE_ENTRY_UNIQUEID, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED); /* flags */ + slapi_search_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc == LDAP_SUCCESS) + { + /* get RUV attributes and construct the RUV */ + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || NULL == entries[0]) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replica ruv tombstone entry for " + "replica %s not found\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + + rc = slapi_entry_attr_find(entries[0], type_ruvElement, &attr); + if (rc != 0) /* ruv attribute is missing - this not allowed */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replica ruv tombstone entry for " + "replica %s does not contain %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), type_ruvElement); + goto done; + } + + /* Check in the tombstone we have retrieved if the local purl is + already present: + rid == 0: the local purl is not present + rid != 0: the local purl is present ==> nothing to do + */ + ruv_init_from_slapi_attr_and_check_purl (attr, &ruv, &rid); + if (ruv) + { + char *generation = NULL; + generation = ruv_get_replica_generation(ruv); + if (NULL != generation) + { + r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy); + + /* Is the local purl in the ruv? (the port or the host could have + changed) + */ + /* A consumer only doesn't have its purl in its ruv */ + if (r->repl_type == REPLICA_TYPE_UPDATABLE) + { + int need_update = 0; + if (rid == 0) + { + /* We can not have more than 1 ruv with the same rid + so we replace it */ + const char *purl = NULL; + + purl = multimaster_get_local_purl(); + ruv_delete_replica(ruv, r->repl_rid); + ruv_add_index_replica(ruv, r->repl_rid, purl, 1); + need_update = 1; /* ruv changed, so write tombstone */ + } + else /* bug 540844: make sure the local supplier rid is first in the ruv */ + { + /* make sure local supplier is first in list */ + ReplicaId first_rid = 0; + char *first_purl = NULL; + ruv_get_first_id_and_purl(ruv, &first_rid, &first_purl); + /* if the local supplier is not first in the list . . . */ + if (rid != first_rid) + { + /* . . . move the local supplier to the beginning of the list */ + ruv_move_local_supplier_to_first(ruv, rid); + need_update = 1; /* must update tombstone also */ + } + } + + /* Update also the directory entry */ + if (need_update) { + /* richm 20010821 bug 556498 + replica_replace_ruv_tombstone acquires the repl_lock, so release + the lock then reacquire it if locked */ + if (isLocked) PR_Unlock(r->repl_lock); + replica_replace_ruv_tombstone(r); + if (isLocked) PR_Lock(r->repl_lock); + } + } + + slapi_ch_free((void **)&generation); + return_value = 0; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "RUV for replica %s is missing replica generation\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Unable to convert %s attribute in entry %s to a replica update vector.\n", + type_ruvElement, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + + } + else /* search failed */ + { + if (LDAP_NO_SUCH_OBJECT == rc) + { + /* The entry doesn't exist: create it */ + rc = replica_create_ruv_tombstone(r); + if (LDAP_SUCCESS != rc) + { + /* + * XXXggood - the following error appears on startup if we try + * to initialize replica RUVs before the backend instance is up. + * It's alarming to see this error, and we should suppress it + * (or avoid trying to configure it) if the backend instance is + * not yet online. + */ + /* + * XXXrichm - you can also get this error when the backend is in + * read only mode c.f. bug 539782 + */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: failed to create replica ruv tombstone " + "entry (%s); LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + goto done; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_configure_ruv: No ruv tombstone found for replica %s. " + "Created a new one\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + return_value = 0; + } + } + else + { + /* see if the suffix is disabled */ + char *state = slapi_mtn_get_state(r->repl_root); + if (state && !strcasecmp(state, "disabled")) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replication disabled for " + "entry (%s); LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + slapi_ch_free_string(&state); + goto done; + } + else if (!r->repl_ruv) /* other error */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_configure_ruv: replication broken for " + "entry (%s); LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + slapi_ch_free_string(&state); + goto done; + } + else /* some error but continue anyway? */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_configure_ruv: Error %d reading tombstone for replica %s.\n", + rc, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + return_value = 0; + } + slapi_ch_free_string(&state); + } + } + + if (NULL != r->min_csn_pl) + { + csnplFree (&r->min_csn_pl); + } + + /* create pending list for min csn if necessary */ + if (ruv_get_smallest_csn_for_replica ((RUV*)object_get_data (r->repl_ruv), + r->repl_rid, &csn) == RUV_SUCCESS) + { + csn_free (&csn); + r->min_csn_pl = NULL; + } + else + { + /* + * The local replica has not generated any of its own CSNs yet. + * We need to watch CSNs being generated and note the first + * locally-generated CSN that's committed. Once that event occurs, + * the RUV is suitable for iteration over locally generated + * changes. + */ + r->min_csn_pl = csnplNew(); + } + +done: + if (NULL != pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy (pb); + } + if (return_value != 0) + { + if (ruv) + ruv_destroy (&ruv); + } + + return return_value; +} + +/* NOTE - this is the only non-api function that performs locking because + it is called by the event queue */ +static void +_replica_update_state (time_t when, void *arg) +{ + int rc; + const char *replica_name = (const char *)arg; + Object *replica_object = NULL; + Replica *r; + Slapi_Mod smod; + LDAPMod *mods[3]; + Slapi_PBlock *pb = NULL; + char *dn = NULL; + + if (NULL == replica_name) + return; + + /* + * replica_get_by_name() will acquire the replica object + * and that could prevent the replica from being destroyed + * until the object_release is called. + */ + replica_object = replica_get_by_name(replica_name); + if (NULL == replica_object) + { + return; + } + + /* We have a reference, so replica won't vanish on us. */ + r = (Replica *)object_get_data(replica_object); + if (NULL == r) + { + goto done; + } + + PR_Lock(r->repl_lock); + + /* replica state is currently being updated + or no CSN was assigned - bail out */ + if (r->state_update_inprogress) + { + PR_Unlock(r->repl_lock); + goto done; + } + + /* This might be a consumer */ + if (!r->repl_csn_assigned) + { + /* EY: the consumer needs to flush ruv to disk. */ + PR_Unlock(r->repl_lock); + replica_write_ruv(r); + goto done; + } + + /* ONREPL update csn generator state of an updatable replica only */ + /* ONREPL state always changes because we update time every second and + we write state to the disk less frequently */ + rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod); + if (rc != 0) + { + PR_Unlock(r->repl_lock); + goto done; + } + + r->state_update_inprogress = PR_TRUE; + r->repl_csn_assigned = PR_FALSE; + + dn = _replica_get_config_dn (r->repl_root); + pb = slapi_pblock_new(); + mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod); + + /* we don't want to held lock during operations since it causes lock contention + and sometimes deadlock. So releasing lock here */ + + PR_Unlock(r->repl_lock); + + /* replica repl_name and new_name attributes do not get changed once + the replica is configured - so it is ok that they are outside replica lock */ + + /* write replica name if it has not been written before */ + if (r->new_name) + { + struct berval *vals[2]; + struct berval val; + LDAPMod mod; + + mods[1] = &mod; + + mod.mod_op = LDAP_MOD_REPLACE; + mod.mod_type = (char*)attr_replicaName; + mod.mod_bvalues = vals; + vals [0] = &val; + vals [1] = NULL; + val.bv_val = r->repl_name; + val.bv_len = strlen (val.bv_val); + mods[2] = NULL; + } + else + { + mods[1] = NULL; + } + + slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + char ebuf[BUFSIZ]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_replica_update_state: " + "failed to update state of csn generator for replica %s: LDAP " + "error - %d\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + } + else + { + r->new_name = PR_FALSE; + } + + /* update RUV - performs its own locking */ + replica_write_ruv (r); + + /* since this is the only place this value is changed and we are + guaranteed that only one thread enters the function, its ok + to change it outside replica lock */ + r->state_update_inprogress = PR_FALSE; + + slapi_ch_free ((void**)&dn); + slapi_pblock_destroy (pb); + slapi_mod_done (&smod); + +done: + if (replica_object) + object_release (replica_object); +} + +void +replica_write_ruv (Replica *r) +{ + int rc; + Slapi_Mod smod; + Slapi_Mod smod_last_modified; + LDAPMod *mods [3]; + Slapi_PBlock *pb; + + PR_ASSERT(r); + + PR_Lock(r->repl_lock); + + if (!r->repl_ruv_dirty) + { + PR_Unlock(r->repl_lock); + return; + } + + PR_ASSERT (r->repl_ruv); + + ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod); + ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified); + + PR_Unlock (r->repl_lock); + + mods [0] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod); + mods [1] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_last_modified); + mods [2] = NULL; + pb = slapi_pblock_new(); + + /* replica name never changes so it is ok to reference it outside the lock */ + slapi_modify_internal_set_pb( + pb, + slapi_sdn_get_dn(r->repl_root), /* only used to select be */ + mods, + NULL, /* controls */ + RUV_STORAGE_ENTRY_UNIQUEID, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + /* Add OP_FLAG_TOMBSTONE_ENTRY so that this doesn't get logged in the Retro ChangeLog */ + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | OP_FLAG_TOMBSTONE_ENTRY); + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + + /* ruv does not exist - create one */ + PR_Lock(r->repl_lock); + + if (rc == LDAP_SUCCESS) + { + r->repl_ruv_dirty = PR_FALSE; + } + else if (rc == LDAP_NO_SUCH_OBJECT) + { + /* this includes an internal operation - but since this only happens + during server startup - its ok that we have lock around it */ + rc = _replica_configure_ruv (r, PR_TRUE); + if (rc == 0) + r->repl_ruv_dirty = PR_FALSE; + } + else /* error */ + { + char ebuf[BUFSIZ]; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_write_ruv: failed to update RUV tombstone for %s; " + "LDAP error - %d\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc); + PR_ASSERT (0); + } + + PR_Unlock(r->repl_lock); + + slapi_mod_done (&smod); + slapi_mod_done (&smod_last_modified); + slapi_pblock_destroy (pb); +} + + +const CSN * +_get_deletion_csn(Slapi_Entry *e) +{ + const CSN *deletion_csn = NULL; + + PR_ASSERT(NULL != e); + if (NULL != e) + { + Slapi_Attr *oc_attr = NULL; + if (entry_attr_find_wsi(e, SLAPI_ATTR_OBJECTCLASS, &oc_attr) == ATTRIBUTE_PRESENT) + { + Slapi_Value *tombstone_value = NULL; + struct berval v; + v.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE; + v.bv_len = strlen(SLAPI_ATTR_VALUE_TOMBSTONE); + if (attr_value_find_wsi(oc_attr, &v, &tombstone_value) == VALUE_PRESENT) + { + deletion_csn = value_get_csn(tombstone_value, CSN_TYPE_VALUE_UPDATED); + } + } + } + return deletion_csn; +} + + +static void +_delete_tombstone(const char *tombstone_dn, const char *uniqueid) +{ + + PR_ASSERT(NULL != tombstone_dn && NULL != uniqueid); + if (NULL == tombstone_dn || NULL == uniqueid) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "_delete_tombstone: " + "NULL tombstone_dn or uniqueid provided.\n"); + } + else + { + int ldaprc; + Slapi_PBlock *pb = slapi_pblock_new(); + slapi_delete_internal_set_pb(pb, tombstone_dn, NULL, /* controls */ + uniqueid, repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_TOMBSTONE_ENTRY); + slapi_delete_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ldaprc); + if (LDAP_SUCCESS != ldaprc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_delete_tombstone: unable to delete tombstone %s, " + "uniqueid %s: %s.\n", tombstone_dn, uniqueid, + ldap_err2string(ldaprc)); + } + slapi_pblock_destroy(pb); + } +} + +static +void get_reap_result (int rc, void *cb_data) +{ + PR_ASSERT (cb_data); + + ((reap_callback_data*)cb_data)->rc = rc; +} + +static +int process_reap_entry (Slapi_Entry *entry, void *cb_data) +{ + char ebuf[BUFSIZ]; + char deletion_csn_str[CSN_STRSIZE]; + char purge_csn_str[CSN_STRSIZE]; + unsigned long *num_entriesp = &((reap_callback_data *)cb_data)->num_entries; + unsigned long *num_purged_entriesp = &((reap_callback_data *)cb_data)->num_purged_entries; + CSN *purge_csn = ((reap_callback_data *)cb_data)->purge_csn; + PRBool *tombstone_reap_stop = ((reap_callback_data *)cb_data)->tombstone_reap_stop; + /* we only ask for the objectclass in the search - the deletion csn is in the + objectclass attribute values - if we need more attributes returned by the + search in the future, see _replica_reap_tombstones below and add more to the + attrs array */ + const CSN *deletion_csn = _get_deletion_csn(entry); + + if ((NULL == deletion_csn || csn_compare(deletion_csn, purge_csn) < 0) && + (!is_ruv_tombstone_entry(entry))) { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_reap_tombstones: removing tombstone %s " + "because its deletion csn (%s) is less than the " + "purge csn (%s).\n", + escape_string(slapi_entry_get_dn(entry), ebuf), + csn_as_string(deletion_csn, PR_FALSE, deletion_csn_str), + csn_as_string(purge_csn, PR_FALSE, purge_csn_str)); + _delete_tombstone(slapi_entry_get_dn(entry), + slapi_entry_get_uniqueid(entry)); + (*num_purged_entriesp)++; + } + else { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_reap_tombstones: NOT removing tombstone " + "%s\n", escape_string(slapi_entry_get_dn(entry),ebuf)); + } + (*num_entriesp)++; + if (*tombstone_reap_stop || g_get_shutdown()) { + return -1; + } + + return 0; +} + + + + +/* This does the actual work of searching for tombstones and deleting them. + This must be called in a separate thread because it may take a long time. +*/ +static void +_replica_reap_tombstones(void *arg) +{ + const char *replica_name = (const char *)arg; + Slapi_PBlock *pb = NULL; + Object *replica_object = NULL; + Replica *replica = NULL; + CSN *purge_csn = NULL; + char ebuf[BUFSIZ]; + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: Beginning tombstone reap for replica %s.\n", + replica_name ? replica_name : "(null)"); + + if (NULL == replica_name) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Warning: Replica name is null in tombstone reap\n"); + goto done; + } + + /* + * replica_get_by_name() will acquire the replica object + * and that could prevent the replica from being destroyed + * until the object_release is called. + */ + replica_object = replica_get_by_name(replica_name); + if (NULL == replica_object) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Warning: Replica object %s is null in tombstone reap\n", replica_name); + goto done; + } + + /* We have a reference, so replica won't vanish on us. */ + replica = (Replica *)object_get_data(replica_object); + if (NULL == replica) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Warning: Replica %s is null in tombstone reap\n", replica_name); + goto done; + } + + if (replica->tombstone_reap_stop) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: Replica %s reap stop flag is set for tombstone reap\n", replica_name); + goto done; + } + + purge_csn = replica_get_purge_csn(replica); + if (NULL != purge_csn) + { + LDAPControl **ctrls; + int oprc; + reap_callback_data cb_data; + char **attrs = NULL; + + /* we just need the objectclass - for the deletion csn + and the dn and nsuniqueid - for possible deletion + saves time to return only 2 attrs + */ + charray_add(&attrs, slapi_ch_strdup("objectclass")); + charray_add(&attrs, slapi_ch_strdup("nsuniqueid")); + + ctrls = (LDAPControl **)slapi_ch_calloc (3, sizeof (LDAPControl *)); + ctrls[0] = create_managedsait_control(); + ctrls[1] = create_backend_control(replica->repl_root); + ctrls[2] = NULL; + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, slapi_sdn_get_dn(replica->repl_root), + LDAP_SCOPE_SUBTREE, "(&(objectclass=nstombstone)(nscpentrydn=*))", + attrs, 0, ctrls, NULL, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0); + + cb_data.rc = 0; + cb_data.num_entries = 0UL; + cb_data.num_purged_entries = 0UL; + cb_data.purge_csn = purge_csn; + cb_data.tombstone_reap_stop = &(replica->tombstone_reap_stop); + + slapi_search_internal_callback_pb (pb, &cb_data /* callback data */, + get_reap_result /* result callback */, + process_reap_entry /* entry callback */, + NULL /* referral callback*/); + + charray_free(attrs); + + oprc = cb_data.rc; + + if (LDAP_SUCCESS != oprc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "_replica_reap_tombstones: failed when searching for " + "tombstones in replica %s: %s. Will try again in %d " + "seconds.\n", escape_string(slapi_sdn_get_dn(replica->repl_root),ebuf), + ldap_err2string(oprc), replica->tombstone_reap_interval); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "_replica_reap_tombstones: purged %d of %d tombstones " + "in replica %s. Will try again in %d " + "seconds.\n", cb_data.num_purged_entries, cb_data.num_entries, + escape_string(slapi_sdn_get_dn(replica->repl_root),ebuf), + replica->tombstone_reap_interval); + } + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: No purge CSN for tombstone reap for replica %s.\n", + replica_name ? replica_name : "(null)"); + } + + PR_Lock(replica->repl_lock); + replica->tombstone_reap_active = PR_FALSE; + PR_Unlock(replica->repl_lock); + +done: + if (NULL != purge_csn) + { + csn_free(&purge_csn); + } + if (NULL != pb) + { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + } + if (NULL != replica_object) + { + object_release(replica_object); + replica_object = NULL; + replica = NULL; + } + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Info: Finished tombstone reap for replica %s.\n", + replica_name ? replica_name : "(null)"); + +} + +/* + We don't want to run the reaper function directly from the event + queue since it may hog the event queue, starving other events. + See bug 604441 + The function eq_cb_reap_tombstones will fire off the actual thread + that does the real work. +*/ +static void +eq_cb_reap_tombstones(time_t when, void *arg) +{ + const char *replica_name = (const char *)arg; + Object *replica_object = NULL; + Replica *replica = NULL; + + if (NULL != replica_name) + { + /* + * replica_get_by_name() will acquire the replica object + * and that could prevent the replica from being destroyed + * until the object_release is called. + */ + replica_object = replica_get_by_name(replica_name); + if (NULL != replica_object) + { + /* We have a reference, so replica won't vanish on us. */ + replica = (Replica *)object_get_data(replica_object); + if (replica) + { + + PR_Lock(replica->repl_lock); + + /* No action if purge is disabled or the previous purge is not done yet */ + if (replica->tombstone_reap_interval != 0 && + replica->tombstone_reap_active == PR_FALSE) + { + /* set the flag here to minimize race conditions */ + replica->tombstone_reap_active = PR_TRUE; + if (PR_CreateThread(PR_USER_THREAD, + _replica_reap_tombstones, (void *)replica_name, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, + SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL) + { + replica->tombstone_reap_active = PR_FALSE; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Error: unable to create the tombstone reap thread for replica %s. " + "Possible system resources problem\n", + replica_name); + } + } + /* reap thread will wait until this lock is released */ + PR_Unlock(replica->repl_lock); + } + object_release(replica_object); + replica_object = NULL; + replica = NULL; + } + } +} + +static char * +_replica_type_as_string (const Replica *r) +{ + switch (r->repl_type) + { + case REPLICA_TYPE_PRIMARY: return "primary"; + case REPLICA_TYPE_READONLY: return "read-only"; + case REPLICA_TYPE_UPDATABLE: return "updatable"; + default: return "unknown"; + } +} + + +static const char *root_glue = + "dn: %s\n" + "objectclass: top\n" + "objectclass: nsTombstone\n" + "objectclass: extensibleobject\n" + "nsuniqueid: %s\n"; + +static int +replica_create_ruv_tombstone(Replica *r) +{ + int return_value = LDAP_LOCAL_ERROR; + char *root_entry_str; + Slapi_Entry *e; + const char *purl = NULL; + RUV *ruv; + struct berval **bvals = NULL; + Slapi_PBlock *pb = NULL; + int rc; + char ebuf[BUFSIZ]; + + PR_ASSERT(NULL != r && NULL != r->repl_root); + root_entry_str = slapi_ch_malloc(strlen(root_glue) + + slapi_sdn_get_ndn_len(r->repl_root) + + strlen(RUV_STORAGE_ENTRY_UNIQUEID) + 1); + sprintf(root_entry_str, root_glue, slapi_sdn_get_ndn(r->repl_root), + RUV_STORAGE_ENTRY_UNIQUEID); + + e = slapi_str2entry(root_entry_str, SLAPI_STR2ENTRY_TOMBSTONE_CHECK); + if (e == NULL) + goto done; + + /* Add ruv */ + if (r->repl_ruv == NULL) + { + CSNGen *gen; + CSN *csn; + char csnstr [CSN_STRSIZE]; + + /* first attempt to write RUV tombstone - need to create RUV */ + gen = (CSNGen *)object_get_data(r->repl_csngen); + PR_ASSERT (gen); + + if (csngen_new_csn(gen, &csn, PR_FALSE /* notify */) == CSN_SUCCESS) + { + (void)csn_as_string(csn, PR_FALSE, csnstr); + csn_free(&csn); + + /* if this is an updateable replica - add its own + element to the RUV so that referrals work correctly */ + if (r->repl_type == REPLICA_TYPE_UPDATABLE) + purl = multimaster_get_local_purl(); + + if (ruv_init_new(csnstr, r->repl_rid, purl, &ruv) == RUV_SUCCESS) + { + r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy); + r->repl_ruv_dirty = PR_TRUE; + return_value = LDAP_SUCCESS; + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Cannot create new replica update vector for %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Cannot obtain CSN for new replica update vector for %s\n", + escape_string(slapi_sdn_get_dn(r->repl_root),ebuf)); + goto done; + } + } + else /* failed to write the entry because DB was not initialized - retry */ + { + ruv = (RUV*) object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + } + + PR_ASSERT (r->repl_ruv); + + rc = ruv_to_bervals(ruv, &bvals); + if (rc != RUV_SUCCESS) + { + goto done; + } + + /* ONREPL this is depricated function but there is currently no better API to use */ + rc = slapi_entry_add_values(e, type_ruvElement, bvals); + if (rc != 0) + { + goto done; + } + + + pb = slapi_pblock_new(); + slapi_add_entry_internal_set_pb( + pb, + e, + NULL /* controls */, + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_TOMBSTONE_ENTRY | OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP); + slapi_add_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value); + if (return_value == LDAP_SUCCESS) + r->repl_ruv_dirty = PR_FALSE; + +done: + if (return_value != LDAP_SUCCESS) + { + slapi_entry_free (e); + } + + if (bvals) + ber_bvecfree(bvals); + + if (pb) + slapi_pblock_destroy(pb); + + slapi_ch_free((void **) &root_entry_str); + + return return_value; +} + + +static void +assign_csn_callback(const CSN *csn, void *data) +{ + Replica *r = (Replica *)data; + Object *ruv_obj; + RUV *ruv; + + PR_ASSERT(NULL != csn); + PR_ASSERT(NULL != r); + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + ruv = (RUV*)object_get_data (ruv_obj); + PR_ASSERT (ruv); + + PR_Lock(r->repl_lock); + + r->repl_csn_assigned = PR_TRUE; + + if (NULL != r->min_csn_pl) + { + if (csnplInsert(r->min_csn_pl, csn) != 0) + { + char ebuf[BUFSIZ]; + char csn_str[CSN_STRSIZE]; /* For logging only */ + /* Ack, we can't keep track of min csn. Punt. */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "assign_csn_callback: " + "failed to insert csn %s for replica %s\n", + csn_as_string(csn, PR_FALSE, csn_str), + escape_string(slapi_sdn_get_dn(r->repl_root), ebuf)); + csnplFree(&r->min_csn_pl); + } + } + + ruv_add_csn_inprogress (ruv, csn); + + PR_Unlock(r->repl_lock); + + object_release (ruv_obj); +} + + +static void +abort_csn_callback(const CSN *csn, void *data) +{ + Replica *r = (Replica *)data; + Object *ruv_obj; + RUV *ruv; + int rc; + + PR_ASSERT(NULL != csn); + PR_ASSERT(NULL != data); + + ruv_obj = replica_get_ruv (r); + PR_ASSERT (ruv_obj); + ruv = (RUV*)object_get_data (ruv_obj); + PR_ASSERT (ruv); + + PR_Lock(r->repl_lock); + + if (NULL != r->min_csn_pl) + { + rc = csnplRemove(r->min_csn_pl, csn); + PR_ASSERT(rc == 0); + } + + ruv_cancel_csn_inprogress (ruv, csn); + PR_Unlock(r->repl_lock); + + object_release (ruv_obj); +} + +static CSN * +_replica_get_purge_csn_nolock(const Replica *r) +{ + static unsigned long a_week = 3600*24*7; + CSN *purge_csn = NULL; + CSN **csns = NULL; + RUV *ruv; + time_t cutoff_time; + time_t max_time_in_csn_list; + int i; + + if (r->repl_purge_delay > 0) + { + /* + * Don't let inactive or obsolete masters in the ruv hold back + * the purge forever: + * - set a graceful period of at least 7 days; + * - set cutoff_time = max(maxcsns) - gracefule_period; + * - the first maxcsn that was generated at or after the cutoff + * time would be the purge csn. + */ + + /* get a sorted list of all maxcsns in ruv in ascend order */ + object_acquire(r->repl_ruv); + ruv = object_get_data(r->repl_ruv); + csns = cl5BuildCSNList (ruv, NULL); + object_release(r->repl_ruv); + + if (csns == NULL) + return NULL; + + /* locate the max csn in the csn list */ + for (i = 0; csns[i]; i++); + max_time_in_csn_list = csn_get_time (csns[i-1]); + + if ( r->repl_purge_delay > a_week ) + { + cutoff_time = max_time_in_csn_list - r->repl_purge_delay; + } + else + { + cutoff_time = max_time_in_csn_list - a_week; + } + for (i = 0; csns[i]; i++) + { + if ( csn_get_time (csns[i]) >= cutoff_time ) + { + purge_csn = csn_dup (csns[i]); + break; + } + } + + /* Subtract purge delay */ + if (purge_csn) + { + csn_set_time(purge_csn, csn_get_time(purge_csn) - r->repl_purge_delay); + } + } + + if (csns) + cl5DestroyCSNList (&csns); + + return purge_csn; +} + +static void +replica_get_referrals_nolock (const Replica *r, char ***referrals) +{ + if(referrals!=NULL) + { + + int hint; + int i= 0; + Slapi_Value *v= NULL; + + if (NULL == r->repl_referral) + { + *referrals = NULL; + } + else + { + /* richm: +1 for trailing NULL */ + *referrals= (char**)slapi_ch_calloc(sizeof(char*),1+slapi_valueset_count(r->repl_referral)); + hint= slapi_valueset_first_value( r->repl_referral, &v ); + while(v!=NULL) + { + const char *s= slapi_value_get_string(v); + if(s!=NULL && s[0]!='\0') + { + (*referrals)[i]= slapi_ch_strdup(s); + i++; + } + hint= slapi_valueset_next_value( r->repl_referral, hint, &v); + } + (*referrals)[i] = NULL; + } + + } +} + +static void +replica_clear_legacy_referrals(const Slapi_DN *repl_root_sdn, + char **referrals, const char *state) +{ + repl_set_mtn_state_and_referrals(repl_root_sdn, state, NULL, NULL, referrals); +} + +static void +replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr) +{ + Slapi_PBlock *pb; + Slapi_Mods smods; + LDAPControl **ctrls; + int rc; + + pb = slapi_pblock_new (); + + slapi_mods_init(&smods, 1); + slapi_mods_add(&smods, LDAP_MOD_DELETE, attr, 0, NULL); + + + ctrls = (LDAPControl**)slapi_ch_malloc (2 * sizeof (LDAPControl*)); + ctrls[0] = create_managedsait_control (); + ctrls[1] = NULL; + + /* remove copiedFrom/copyingFrom first */ + slapi_modify_internal_set_pb (pb, slapi_sdn_get_dn (repl_root_sdn), + slapi_mods_get_ldapmods_passout (&smods), ctrls, + NULL /*uniqueid */, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION) , + 0 /* operation_flags */); + + slapi_modify_internal_pb (pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + if (rc != LDAP_SUCCESS) + { + char ebuf[BUFSIZ]; + + /* this is not a fatal error because the attribute may not be there */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_remove_legacy_attr: " + "failed to remove legacy attribute %s for replica %s; LDAP error - %d\n", + attr, escape_string(slapi_sdn_get_dn(repl_root_sdn),ebuf), rc); + } + + slapi_mods_done (&smods); + slapi_pblock_destroy (pb); +} + +static int +replica_log_ruv_elements_nolock (const Replica *r) +{ + int rc = 0; + slapi_operation_parameters op_params; + RUV *ruv; + char *repl_gen; + CSN *csn = NULL; + + ruv = (RUV*) object_get_data (r->repl_ruv); + PR_ASSERT (ruv); + + if ((ruv_get_min_csn(ruv, &csn) == RUV_SUCCESS) && csn) + { + /* we log it as a delete operation to have the least number of fields + to set. the entry can be identified by a special target uniqueid and + special target dn */ + memset (&op_params, 0, sizeof (op_params)); + op_params.operation_type = SLAPI_OPERATION_DELETE; + op_params.target_address.dn = START_ITERATION_ENTRY_DN; + op_params.target_address.uniqueid = START_ITERATION_ENTRY_UNIQUEID; + op_params.csn = csn; + repl_gen = ruv_get_replica_generation (ruv); + + rc = cl5WriteOperation(r->repl_name, repl_gen, &op_params, PR_FALSE); + if (rc == CL5_SUCCESS) + rc = 0; + else + rc = -1; + + slapi_ch_free ((void**)&repl_gen); + csn_free (&csn); + } + + return rc; +} + +void +replica_set_purge_delay(Replica *r, PRUint32 purge_delay) +{ + PR_ASSERT(r); + PR_Lock(r->repl_lock); + r->repl_purge_delay = purge_delay; + PR_Unlock(r->repl_lock); +} + +void +replica_set_tombstone_reap_interval (Replica *r, long interval) +{ + char *repl_name; + + PR_Lock(r->repl_lock); + + /* + * Leave the event there to purge the existing tombstones + * if we are about to turn off tombstone creation + */ + if (interval > 0 && r->repl_eqcxt_tr && r->tombstone_reap_interval != interval) + { + int found; + + repl_name = slapi_eq_get_arg (r->repl_eqcxt_tr); + slapi_ch_free ((void**)&repl_name); + found = slapi_eq_cancel (r->repl_eqcxt_tr); + slapi_log_error (SLAPI_LOG_REPL, NULL, + "tombstone_reap event (interval=%d) was %s\n", + r->tombstone_reap_interval, (found ? "cancelled" : "not found")); + r->repl_eqcxt_tr = NULL; + } + r->tombstone_reap_interval = interval; + if ( interval > 0 && r->repl_eqcxt_tr == NULL ) + { + repl_name = slapi_ch_strdup (r->repl_name); + r->repl_eqcxt_tr = slapi_eq_repeat (eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval); + slapi_log_error (SLAPI_LOG_REPL, NULL, + "tombstone_reap event (interval=%d) was %s\n", + r->tombstone_reap_interval, (r->repl_eqcxt_tr ? "scheduled" : "not scheduled successfully")); + } + PR_Unlock(r->repl_lock); +} + +/* Update the tombstone entry to reflect the content of the ruv */ +static void +replica_replace_ruv_tombstone(Replica *r) +{ + Slapi_PBlock *pb = NULL; + char *dn; + int rc; + + Slapi_Mod smod; + Slapi_Mod smod_last_modified; + LDAPMod *mods [3]; + + PR_ASSERT(NULL != r && NULL != r->repl_root); + + PR_Lock(r->repl_lock); + + PR_ASSERT (r->repl_ruv); + ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod); + ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified); + + dn = _replica_get_config_dn (r->repl_root); + mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod); + mods[1] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod_last_modified); + + PR_Unlock (r->repl_lock); + + mods [2] = NULL; + pb = slapi_pblock_new(); + + slapi_modify_internal_set_pb( + pb, + (char*)slapi_sdn_get_dn (r->repl_root), /* only used to select be */ + mods, + NULL, /* controls */ + RUV_STORAGE_ENTRY_UNIQUEID, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP); + + slapi_modify_internal_pb (pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + + if (rc != LDAP_SUCCESS) + { + if ((rc != LDAP_NO_SUCH_OBJECT) || !replica_is_state_flag_set(r, REPLICA_IN_USE)) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_replace_ruv_tombstone: " + "failed to update replication update vector for replica %s: LDAP " + "error - %d\n", (char*)slapi_sdn_get_dn (r->repl_root), rc); + } + } + + slapi_ch_free ((void**)&dn); + slapi_pblock_destroy (pb); + slapi_mod_done (&smod); + slapi_mod_done (&smod_last_modified); +} + +void +replica_update_ruv_consumer(Replica *r, RUV *supplier_ruv) +{ + ReplicaId supplier_id = 0; + char *supplier_purl = NULL; + + if ( ruv_get_first_id_and_purl(supplier_ruv, &supplier_id, &supplier_purl) == RUV_SUCCESS ) + { + RUV *local_ruv = NULL; + + PR_Lock(r->repl_lock); + + local_ruv = (RUV*)object_get_data (r->repl_ruv); + PR_ASSERT (local_ruv); + + if ( ruv_local_contains_supplier(local_ruv, supplier_id) == 0 ) + { + if ( r->repl_type == REPLICA_TYPE_UPDATABLE ) + { + /* Add the new ruv right after the consumer own purl */ + ruv_add_index_replica(local_ruv, supplier_id, supplier_purl, 2); + } + else + { + /* This is a consumer only, add it first */ + ruv_add_index_replica(local_ruv, supplier_id, supplier_purl, 1); + } + } + else + { + /* Replace it */ + ruv_replace_replica_purl(local_ruv, supplier_id, supplier_purl); + } + PR_Unlock(r->repl_lock); + + /* Update also the directory entry */ + replica_replace_ruv_tombstone(r); + } +} + +void +replica_set_ruv_dirty(Replica *r) +{ + PR_ASSERT(r); + PR_Lock(r->repl_lock); + r->repl_ruv_dirty = PR_TRUE; + PR_Unlock(r->repl_lock); +} + +PRBool +replica_is_state_flag_set(Replica *r, PRInt32 flag) +{ + PR_ASSERT(r); + if (r) + return (r->repl_state_flags & flag); + else + return PR_FALSE; +} + +void +replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear) +{ + if (r == NULL) + return; + + PR_Lock(r->repl_lock); + + if (clear) + { + r->repl_state_flags &= ~flag; + } + else + { + r->repl_state_flags |= flag; + } + + PR_Unlock(r->repl_lock); +} + +/* replica just came back online, probably after data was reloaded */ +void +replica_enable_replication (Replica *r) +{ + int rc; + + PR_ASSERT(r); + + /* prevent creation of new agreements until the replica is enabled */ + PR_Lock(r->agmt_lock); + + /* retrieve new ruv */ + rc = replica_reload_ruv (r); + if (rc) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_enable_replication: " + "reloading ruv failed\n"); + /* What to do ? */ + } + + /* Replica came back online, Check if the total update was terminated. + If flag is still set, it was not terminated, therefore the data is + very likely to be incorrect, and we should not restart Replication threads... + */ + if (!replica_is_state_flag_set(r, REPLICA_TOTAL_IN_PROGRESS)){ + /* restart outbound replication */ + start_agreements_for_replica (r, PR_TRUE); + + /* enable ruv state update */ + replica_set_enabled (r, PR_TRUE); + } + + /* mark the replica as being available for updates */ + replica_relinquish_exclusive_access(r, 0, 0); + + replica_set_state_flag(r, REPLICA_AGREEMENTS_DISABLED, PR_TRUE /* clear */); + PR_Unlock(r->agmt_lock); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_enable_replication: " + "replica %s is relinquished\n", + slapi_sdn_get_ndn (replica_get_root (r))); +} + +/* replica is about to be taken offline */ +void +replica_disable_replication (Replica *r, Object *r_obj) +{ + char *current_purl = NULL; + char *p_locking_purl = NULL; + char *locking_purl = NULL; + int junkrc; + ReplicaId junkrid; + PRBool isInc = PR_FALSE; /* get exclusive access, but not for inc update */ + RUV *repl_ruv = NULL; + + /* prevent creation of new agreements until the replica is disabled */ + PR_Lock(r->agmt_lock); + + /* stop ruv update */ + replica_set_enabled (r, PR_FALSE); + + /* disable outbound replication */ + start_agreements_for_replica (r, PR_FALSE); + + /* close the corresponding changelog file */ + /* close_changelog_for_replica (r_obj); */ + + /* mark the replica as being unavailable for updates */ + /* If an incremental update is in progress, we want to wait until it is + finished until we get exclusive access to the replica, because we have + to make sure no operations are in progress - it messes up replication + when a restore is in progress but we are still adding replicated entries + from a supplier + */ + repl_ruv = (RUV*) object_get_data (r->repl_ruv); + junkrc = ruv_get_first_id_and_purl(repl_ruv, &junkrid, &p_locking_purl); + locking_purl = slapi_ch_strdup(p_locking_purl); + p_locking_purl = NULL; + repl_ruv = NULL; + while (!replica_get_exclusive_access(r, &isInc, 0, 0, "replica_disable_replication", + ¤t_purl)) { + if (!isInc) /* already locked, but not by inc update - break */ + break; + isInc = PR_FALSE; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "replica_disable_replication: " + "replica %s is already locked by (%s) for incoming " + "incremental update; sleeping 100ms\n", + slapi_sdn_get_ndn (replica_get_root (r)), + current_purl ? current_purl : "unknown"); + slapi_ch_free_string(¤t_purl); + DS_Sleep(PR_MillisecondsToInterval(100)); + } + + slapi_ch_free_string(¤t_purl); + slapi_ch_free_string(&locking_purl); + replica_set_state_flag(r, REPLICA_AGREEMENTS_DISABLED, PR_FALSE); + PR_Unlock(r->agmt_lock); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_disable_replication: " + "replica %s is acquired\n", + slapi_sdn_get_ndn (replica_get_root (r))); +} + +static void +start_agreements_for_replica (Replica *r, PRBool start) +{ + Object *agmt_obj; + Repl_Agmt *agmt; + + agmt_obj = agmtlist_get_first_agreement_for_replica (r); + while (agmt_obj) + { + agmt = (Repl_Agmt*)object_get_data (agmt_obj); + PR_ASSERT (agmt); + + if (start) + agmt_start (agmt); + else /* stop */ + agmt_stop (agmt); + + agmt_obj = agmtlist_get_next_agreement_for_replica (r, agmt_obj); + } +} + +int replica_start_agreement(Replica *r, Repl_Agmt *ra) +{ + int ret = 0; + + if (r == NULL) return -1; + + PR_Lock(r->agmt_lock); + + if (!replica_is_state_flag_set(r, REPLICA_AGREEMENTS_DISABLED)) { + ret = agmt_start(ra); /* Start the replication agreement */ + } + + PR_Unlock(r->agmt_lock); + return ret; +} + +/* + * A callback function registed as op->o_csngen_handler and + * called by backend ops to generate opcsn. + */ +CSN * +replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn ) +{ + CSN *opcsn = NULL; + Object *replica_obj; + + replica_obj = replica_get_replica_for_op (pb); + if (NULL != replica_obj) + { + Replica *replica = (Replica*) object_get_data (replica_obj); + if ( NULL != replica ) + { + Slapi_Operation *op; + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + if ( replica->repl_type != REPLICA_TYPE_READONLY || + operation_is_flag_set (op, OP_FLAG_LEGACY_REPLICATION_DN )) + { + Object *gen_obj = replica_get_csngen (replica); + if (NULL != gen_obj) + { + CSNGen *gen = (CSNGen*) object_get_data (gen_obj); + if (NULL != gen) + { + /* The new CSN should be greater than the base CSN */ + csngen_new_csn (gen, &opcsn, PR_FALSE /* don't notify */); + if (csn_compare (opcsn, basecsn) <= 0) + { + char opcsnstr[CSN_STRSIZE], basecsnstr[CSN_STRSIZE]; + char opcsn2str[CSN_STRSIZE]; + + csn_as_string (opcsn, PR_FALSE, opcsnstr); + csn_as_string (basecsn, PR_FALSE, basecsnstr); + csn_free ( &opcsn ); + csngen_adjust_time (gen, basecsn); + csngen_new_csn (gen, &opcsn, PR_FALSE /* don't notify */); + csn_as_string (opcsn, PR_FALSE, opcsn2str); + slapi_log_error (SLAPI_LOG_FATAL, NULL, + "replica_generate_next_csn: " + "opcsn=%s <= basecsn=%s, adjusted opcsn=%s\n", + opcsnstr, basecsnstr, opcsn2str); + } + /* + * Insert opcsn into the csn pending list. + * This is the notify effect in csngen_new_csn(). + */ + assign_csn_callback (opcsn, (void *)replica); + } + object_release (gen_obj); + } + } + } + object_release (replica_obj); + } + + return opcsn; +} + +/* + * A callback function registed as op->o_replica_attr_handler and + * called by backend ops to get replica attributes. + */ +int +replica_get_attr ( Slapi_PBlock *pb, const char* type, void *value ) +{ + int rc = -1; + + Object *replica_obj; + replica_obj = replica_get_replica_for_op (pb); + if (NULL != replica_obj) + { + Replica *replica = (Replica*) object_get_data (replica_obj); + if ( NULL != replica ) + { + if (strcasecmp (type, type_replicaTombstonePurgeInterval) == 0) + { + *((int*)value) = replica->tombstone_reap_interval; + rc = 0; + } + else if (strcasecmp (type, type_replicaPurgeDelay) == 0) + { + *((int*)value) = replica->repl_purge_delay; + rc = 0; + } + } + object_release (replica_obj); + } + + return rc; +} diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c new file mode 100644 index 00000000..df2573a0 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica_config.c @@ -0,0 +1,750 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_replica_config.c - replica configuration over ldap */ +#include <ctype.h> /* for isdigit() */ +#include "repl.h" /* ONREPL - this is bad */ +#include "repl5.h" +#include "cl5_api.h" + +#define CONFIG_BASE "cn=mapping tree,cn=config" +#define CONFIG_FILTER "(objectclass=nsDS5Replica)" +#define TASK_ATTR "nsds5Task" +#define CL2LDIF_TASK "CL2LDIF" +#define CLEANRUV "CLEANRUV" +#define CLEANRUVLEN 8 + +int slapi_log_urp = SLAPI_LOG_REPL; + +/* Forward Declartions */ +static int replica_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int replica_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); +static int replica_config_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +static int replica_config_change_type_and_id (Replica *r, const char *new_type, const char *new_id, char *returntext, int apply_mods); +static int replica_config_change_updatedn (Replica *r, const LDAPMod *mod, char *returntext, int apply_mods); +static int replica_config_change_flags (Replica *r, const char *new_flags, char *returntext, int apply_mods); +static int replica_execute_task (Object *r, const char *task_name, char *returntext, int apply_mods); +static int replica_execute_cl2ldif_task (Object *r, char *returntext); +static int replica_execute_cleanruv_task (Object *r, ReplicaId rid, char *returntext); + +static multimaster_mtnode_extension * _replica_config_get_mtnode_ext (const Slapi_Entry *e); + +static PRLock *s_configLock; + +static int +dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg) +{ + *returncode = LDAP_UNWILLING_TO_PERFORM; + return SLAPI_DSE_CALLBACK_ERROR; +} + +int +replica_config_init() +{ + s_configLock = PR_NewLock (); + if (s_configLock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_init: " + "failed to cretate configuration lock; NSPR error - %d\n", + PR_GetError ()); + return -1; + } + + /* config DSE must be initialized before we get here */ + slapi_config_register_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_add, NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_modify,NULL); + slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, dont_allow_that, NULL); + slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_delete,NULL); + slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_search,NULL); + return 0; +} + +void +replica_config_destroy () +{ + if (s_configLock) + { + PR_DestroyLock (s_configLock); + s_configLock = NULL; + } + + /* config DSE must be initialized before we get here */ + slapi_config_remove_callback(SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_add); + slapi_config_remove_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_modify); + slapi_config_remove_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, dont_allow_that); + slapi_config_remove_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_delete); + slapi_config_remove_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, CONFIG_BASE, LDAP_SCOPE_SUBTREE, + CONFIG_FILTER, replica_config_search); +} + +static int +replica_config_add (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *errorbuf, void *arg) +{ + Replica *r = NULL; + multimaster_mtnode_extension *mtnode_ext; + char *replica_root = (char*)slapi_entry_attr_get_charptr (e, attr_replicaRoot); + char buf [BUFSIZ]; + char *errortext = errorbuf ? errorbuf : buf; + + if (errorbuf) + { + errorbuf[0] = '\0'; + } + + *returncode = LDAP_SUCCESS; + + PR_Lock (s_configLock); + + /* add the dn to the dn hash so we can tell this replica is being configured */ + replica_add_by_dn(replica_root); + + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + { + sprintf (errortext, "replica already configured for %s", replica_root); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: %s\n", errortext); + *returncode = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + /* create replica object */ + r = replica_new_from_entry (e, errortext, PR_TRUE /* is a newly added entry */); + if (r == NULL) + { + *returncode = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* Set the mapping tree node state, and the referrals from the RUV */ + /* if this server is a 4.0 consumer the referrals are set by legacy plugin */ + if (!replica_is_legacy_consumer (r)) + consumer5_set_mapping_tree_state_for_replica(r, NULL); + + /* ONREPL if replica is added as writable we need to execute protocol that + introduces new writable replica to the topology */ + + mtnode_ext->replica = object_new (r, replica_destroy); /* Refcnt is 1 */ + + /* add replica object to the hash */ + *returncode = replica_add_by_name (replica_get_name (r), mtnode_ext->replica); /* Increments object refcnt */ + /* delete the dn from the dn hash - done with configuration */ + replica_delete_by_dn(replica_root); + +done: + + PR_Unlock (s_configLock); + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&replica_root); + + if (*returncode != LDAP_SUCCESS) + { + if (mtnode_ext->replica) + object_release (mtnode_ext->replica); + return SLAPI_DSE_CALLBACK_ERROR; + } + else + return SLAPI_DSE_CALLBACK_OK; +} + +static int +replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, + int *returncode, char *returntext, void *arg) +{ + int rc= 0; + LDAPMod **mods; + int i, apply_mods; + multimaster_mtnode_extension *mtnode_ext; + Replica *r = NULL; + char *replica_root = NULL; + char buf [BUFSIZ]; + char *errortext = returntext ? returntext : buf; + char *config_attr, *config_attr_value; + Slapi_Operation *op; + void *identity; + + if (returntext) + { + returntext[0] = '\0'; + } + *returncode = LDAP_SUCCESS; + + /* just let internal operations originated from replication plugin to go through */ + slapi_pblock_get (pb, SLAPI_OPERATION, &op); + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + + if (operation_is_flag_set(op, OP_FLAG_INTERNAL) && + (identity == repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION))) + { + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; + } + + replica_root = (char*)slapi_entry_attr_get_charptr (e, attr_replicaRoot); + + PR_Lock (s_configLock); + + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + object_acquire (mtnode_ext->replica); + + if (mtnode_ext->replica == NULL) + { + sprintf (errortext, "replica does not exist for %s", replica_root); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + *returncode = LDAP_OPERATIONS_ERROR; + goto done; + } + + r = object_get_data (mtnode_ext->replica); + PR_ASSERT (r); + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (apply_mods = 0; apply_mods <= 1; apply_mods++) + { + /* we only allow the replica ID and type to be modified together e.g. + if converting a read only replica to a master or vice versa - + we will need to change both the replica ID and the type at the same + time - we must disallow changing the replica ID if the type is not + being changed and vice versa + */ + char *new_repl_id = NULL; + char *new_repl_type = NULL; + + if (*returncode != LDAP_SUCCESS) + break; + + for (i = 0; (mods[i] && (LDAP_SUCCESS == rc)); i++) + { + if (*returncode != LDAP_SUCCESS) + break; + + config_attr = (char *) mods[i]->mod_type; + PR_ASSERT (config_attr); + + /* disallow modifications or removal of replica root, + replica name and replica state attributes */ + if (strcasecmp (config_attr, attr_replicaRoot) == 0 || + strcasecmp (config_attr, attr_replicaName) == 0 || + strcasecmp (config_attr, attr_state) == 0) + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + sprintf (errortext, "modification of %s attribute is not allowed", + config_attr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + } + /* this is a request to delete an attribute */ + else if (mods[i]->mod_op & LDAP_MOD_DELETE || mods[i]->mod_bvalues == NULL + || mods[i]->mod_bvalues[0]->bv_val == NULL) + { + /* currently, you can only remove referral, + legacy consumer or bind dn attribute */ + if (strcasecmp (config_attr, attr_replicaBindDn) == 0) + { + *returncode = replica_config_change_updatedn (r, mods[i], errortext, apply_mods); + } + else if (strcasecmp (config_attr, attr_replicaReferral) == 0) + { + if (apply_mods) { + replica_set_referrals(r, NULL); + if (!replica_is_legacy_consumer (r)) { + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + } + } + else if (strcasecmp (config_attr, type_replicaLegacyConsumer) == 0) + { + if (apply_mods) + replica_set_legacy_consumer (r, PR_FALSE); + } + else + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + sprintf (errortext, "deletion of %s attribute is not allowed", config_attr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + } + } + else /* modify an attribute */ + { + config_attr_value = (char *) mods[i]->mod_bvalues[0]->bv_val; + + if (strcasecmp (config_attr, attr_replicaBindDn) == 0) + { + *returncode = replica_config_change_updatedn (r, mods[i], + errortext, apply_mods); + } + else if (strcasecmp (config_attr, attr_replicaType) == 0) + { + new_repl_type = slapi_ch_strdup(config_attr_value); + } + else if (strcasecmp (config_attr, attr_replicaId) == 0) + { + new_repl_id = slapi_ch_strdup(config_attr_value); + } + else if (strcasecmp (config_attr, attr_flags) == 0) + { + *returncode = replica_config_change_flags (r, config_attr_value, + errortext, apply_mods); + } + else if (strcasecmp (config_attr, TASK_ATTR) == 0) + { + *returncode = replica_execute_task (mtnode_ext->replica, config_attr_value, + errortext, apply_mods); + } + else if (strcasecmp (config_attr, attr_replicaReferral) == 0) + { + if (apply_mods) + { + Slapi_Mod smod; + Slapi_ValueSet *vs= slapi_valueset_new(); + slapi_mod_init_byref(&smod,mods[i]); + slapi_valueset_set_from_smod(vs, &smod); + replica_set_referrals (r, vs); + slapi_mod_done(&smod); + slapi_valueset_free(vs); + if (!replica_is_legacy_consumer (r)) { + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + } + } + else if (strcasecmp (config_attr, type_replicaPurgeDelay) == 0) + { + if (apply_mods && config_attr_value && config_attr_value[0]) + { + PRUint32 delay; + if (isdigit (config_attr_value[0])) + { + delay = (unsigned int)atoi(config_attr_value); + replica_set_purge_delay(r, delay); + } + else + *returncode = LDAP_OPERATIONS_ERROR; + } + } + else if (strcasecmp (config_attr, type_replicaTombstonePurgeInterval) == 0) + { + if (apply_mods && config_attr_value && config_attr_value[0]) + { + long interval; + interval = atol (config_attr_value); + replica_set_tombstone_reap_interval (r, interval); + } + } + else if (strcasecmp (config_attr, type_replicaLegacyConsumer) == 0) + { + if (apply_mods) + { + PRBool legacy = (strcasecmp (config_attr_value, "on") == 0) || + (strcasecmp (config_attr_value, "true") == 0) || + (strcasecmp (config_attr_value, "yes") == 0) || + (strcasecmp (config_attr_value, "1") == 0); + + replica_set_legacy_consumer (r, legacy); + } + } + /* ignore modifiers attributes added by the server */ + else if (strcasecmp (config_attr, "modifytimestamp") == 0 || + strcasecmp (config_attr, "modifiersname") == 0) + { + *returncode = LDAP_SUCCESS; + } + else + { + *returncode = LDAP_UNWILLING_TO_PERFORM; + sprintf (errortext, "modification of attribute %s is not allowed in replica entry", config_attr); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_modify: %s\n", + errortext); + } + } + } + + if (new_repl_id || new_repl_type) + { + *returncode = replica_config_change_type_and_id(r, new_repl_type, + new_repl_id, errortext, + apply_mods); + slapi_ch_free_string(&new_repl_id); + slapi_ch_free_string(&new_repl_type); + } + } + +done: + if (mtnode_ext->replica) + object_release (mtnode_ext->replica); + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)&replica_root); + + PR_Unlock (s_configLock); + + if (*returncode != LDAP_SUCCESS) + { + return SLAPI_DSE_CALLBACK_ERROR; + } + else + { + return SLAPI_DSE_CALLBACK_OK; + } +} + +static int +replica_config_delete (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, + int *returncode, char *returntext, void *arg) +{ + multimaster_mtnode_extension *mtnode_ext; + Replica *r; + + PR_Lock (s_configLock); + + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + { + /* remove object from the hash */ + r = (Replica*)object_get_data (mtnode_ext->replica); + PR_ASSERT (r); + replica_delete_by_name (replica_get_name (r)); + object_release (mtnode_ext->replica); + mtnode_ext->replica = NULL; + } + + PR_Unlock (s_configLock); + + *returncode = LDAP_SUCCESS; + return SLAPI_DSE_CALLBACK_OK; +} + +static int +replica_config_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, + char *returntext, void *arg) +{ + multimaster_mtnode_extension *mtnode_ext; + int changeCount = 0; + char val [64]; + + /* add attribute that contains number of entries in the changelog for this replica */ + + PR_Lock (s_configLock); + + /* if we have no changelog - we have no changes */ + if (cl5GetState () == CL5_STATE_OPEN) + { + mtnode_ext = _replica_config_get_mtnode_ext (e); + PR_ASSERT (mtnode_ext); + + if (mtnode_ext->replica) + { + object_acquire (mtnode_ext->replica); + changeCount = cl5GetOperationCount (mtnode_ext->replica); + object_release (mtnode_ext->replica); + } + } + + sprintf (val, "%d", changeCount); + slapi_entry_add_string (e, type_replicaChangeCount, val); + + PR_Unlock (s_configLock); + + return SLAPI_DSE_CALLBACK_OK; +} + +static int +replica_config_change_type_and_id (Replica *r, const char *new_type, + const char *new_id, char *returntext, + int apply_mods) +{ + int type; + ReplicaType oldtype; + ReplicaId rid; + ReplicaId oldrid; + + PR_ASSERT (r); + + oldtype = replica_get_type(r); + oldrid = replica_get_rid(r); + if (new_type == NULL) /* by default - replica is read-only */ + { + type = REPLICA_TYPE_READONLY; + } + else + { + type = atoi (new_type); + if (type <= REPLICA_TYPE_UNKNOWN || type >= REPLICA_TYPE_END) + { + sprintf (returntext, "invalid replica type %d", type); + return LDAP_OPERATIONS_ERROR; + } + } + + /* disallow changing type to itself just to permit a replica ID change */ + if (oldtype == type) + { + sprintf (returntext, "replica type is already %d - not changing", type); + return LDAP_OPERATIONS_ERROR; + } + + if (type == REPLICA_TYPE_READONLY) + { + rid = READ_ONLY_REPLICA_ID; /* default rid for read only */ + } + else if (!new_id) + { + sprintf(returntext, "a replica ID is required when changing replica type to read-write"); + return LDAP_UNWILLING_TO_PERFORM; + } + else + { + int temprid = atoi (new_id); + if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID) + { + sprintf(returntext, + "attribute %s must have a value greater than 0 " + "and less than %d", + attr_replicaId, READ_ONLY_REPLICA_ID); + return LDAP_UNWILLING_TO_PERFORM; + } + else + { + rid = (ReplicaId)temprid; + } + } + + /* error if old rid == new rid */ + if (oldrid == rid) + { + sprintf (returntext, "replica ID is already %d - not changing", rid); + return LDAP_OPERATIONS_ERROR; + } + + if (apply_mods) + { + replica_set_type (r, type); + replica_set_rid(r, rid); + + /* Set the mapping tree node, and the list of referrals */ + /* if this server is a 4.0 consumer the referrals are set by legacy plugin */ + if (!replica_is_legacy_consumer(r)) + consumer5_set_mapping_tree_state_for_replica(r, NULL); + } + + return LDAP_SUCCESS; +} + +static int +replica_config_change_updatedn (Replica *r, const LDAPMod *mod, char *returntext, + int apply_mods) +{ + PR_ASSERT (r); + + if (apply_mods) + { + Slapi_Mod smod; + Slapi_ValueSet *vs= slapi_valueset_new(); + slapi_mod_init_byref(&smod, (LDAPMod *)mod); /* cast away const */ + slapi_valueset_set_from_smod(vs, &smod); + replica_set_updatedn(r, vs, mod->mod_op); + slapi_mod_done(&smod); + slapi_valueset_free(vs); + } + + return LDAP_SUCCESS; +} + +static int replica_config_change_flags (Replica *r, const char *new_flags, + char *returntext, int apply_mods) +{ + PR_ASSERT (r); + + if (apply_mods) + { + PRUint32 flags; + + flags = atol (new_flags); + + replica_replace_flags (r, flags); + } + + return LDAP_SUCCESS; +} + +static int replica_execute_task (Object *r, const char *task_name, char *returntext, + int apply_mods) +{ + + if (strcasecmp (task_name, CL2LDIF_TASK) == 0) + { + if (apply_mods) + { + return replica_execute_cl2ldif_task (r, returntext); + } + else + return LDAP_SUCCESS; + } + else if (strncasecmp (task_name, CLEANRUV, CLEANRUVLEN) == 0) + { + int temprid = atoi(&(task_name[CLEANRUVLEN])); + if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID){ + sprintf(returntext, "Invalid replica id for task - %s", task_name); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + if (apply_mods) + { + return replica_execute_cleanruv_task (r, (ReplicaId)temprid, returntext); + } + else + return LDAP_SUCCESS; + } + else + { + sprintf (returntext, "unsupported replica task - %s", task_name); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + +} + +static int replica_execute_cl2ldif_task (Object *r, char *returntext) +{ + int rc; + Object *rlist [2]; + Replica *replica; + char fName [MAXPATHLEN]; + char *clDir; + + if (cl5GetState () != CL5_STATE_OPEN) + { + sprintf (returntext, "changelog is not open"); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_cl2ldif_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + + rlist[0] = r; + rlist[1] = NULL; + + /* file is stored in the changelog directory and is named + <replica name>.ldif */ + clDir = cl5GetDir (); + PR_ASSERT (clDir); + + replica = (Replica*)object_get_data (r); + PR_ASSERT (replica); + + sprintf (fName, "%s/%s.ldif", clDir, replica_get_name (replica)); + slapi_ch_free ((void**)&clDir); + + rc = cl5ExportLDIF (fName, rlist); + if (rc != CL5_SUCCESS) + { + sprintf (returntext, "failed to export changelog data to file %s; " + "changelog error - %d", fName, rc); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "replica_execute_cl2ldif_task: %s\n", returntext); + return LDAP_OPERATIONS_ERROR; + } + + return LDAP_SUCCESS; +} + +static multimaster_mtnode_extension * +_replica_config_get_mtnode_ext (const Slapi_Entry *e) +{ + const char *replica_root; + Slapi_DN *sdn = NULL; + mapping_tree_node *mtnode; + multimaster_mtnode_extension *ext = NULL; + char ebuf[BUFSIZ]; + + /* retirve root of the tree for which replica is configured */ + replica_root = slapi_entry_attr_get_charptr (e, attr_replicaRoot); + if (replica_root == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: " + "configuration entry %s missing %s attribute\n", + escape_string(slapi_entry_get_dn((Slapi_Entry *)e), ebuf), + attr_replicaRoot); + return NULL; + } + + sdn = slapi_sdn_new_dn_passin (replica_root); + + /* locate mapping tree node for the specified subtree */ + mtnode = slapi_get_mapping_tree_node_by_dn (sdn); + if (mtnode == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_config_add: " + "failed to locate mapping tree node for dn %s\n", + escape_string(slapi_sdn_get_dn(sdn), ebuf)); + } + else + { + /* check if replica object already exists for the specified subtree */ + ext = (multimaster_mtnode_extension *)repl_con_get_ext (REPL_CON_EXT_MTNODE, mtnode); + } + + slapi_sdn_free (&sdn); + + return ext; +} + +static int +replica_execute_cleanruv_task (Object *r, ReplicaId rid, char *returntext) +{ + int rc = 0; + Object *RUVObj; + RUV *local_ruv = NULL; + Replica *replica = (Replica*)object_get_data (r); + + PR_ASSERT (replica); + + RUVObj = replica_get_ruv(replica); + PR_ASSERT(RUVObj); + local_ruv = (RUV*)object_get_data (RUVObj); + /* Need to check that : + * - rid is not the local one + * - rid is not the last one + */ + if ((replica_get_rid(replica) == rid) || + (ruv_replica_count(local_ruv) <= 1)) { + return LDAP_UNWILLING_TO_PERFORM; + } + rc = ruv_delete_replica(local_ruv, rid); + replica_set_ruv_dirty(replica); + replica_write_ruv(replica); + object_release(RUVObj); + + /* Update Mapping Tree to reflect RUV changes */ + consumer5_set_mapping_tree_state_for_replica(replica, NULL); + + if (rc != RUV_SUCCESS){ + return LDAP_OPERATIONS_ERROR; + } + return LDAP_SUCCESS; +} + + diff --git a/ldap/servers/plugins/replication/repl5_replica_dnhash.c b/ldap/servers/plugins/replication/repl5_replica_dnhash.c new file mode 100644 index 00000000..4b9f42b9 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica_dnhash.c @@ -0,0 +1,189 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_replica_dnhash.c */ + +#include "repl5.h" +#include "plhash.h" + +/* global data */ +static PLHashTable *s_hash; +static PRRWLock *s_lock; + +/* Forward declarations */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg); + +int replica_init_dn_hash () +{ + /* allocate table */ + s_hash = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, + PL_CompareValues, NULL, NULL); + if (s_hash == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_dn_hash: " + "failed to allocate hash table; NSPR error - %d\n", + PR_GetError ()); + return -1; + } + + /* create lock */ + s_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "replica_dnhash_lock"); + if (s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_dn_hash: " + "failed to create lock; NSPR error - %d\n", + PR_GetError ()); + replica_destroy_dn_hash (); + return -1; + } + + return 0; +} + +void replica_destroy_dn_hash () +{ + /* destroy the content */ + PL_HashTableEnumerateEntries(s_hash, replica_destroy_hash_entry, NULL); + + if (s_hash) + PL_HashTableDestroy(s_hash); + + if (s_lock) + PR_DestroyRWLock (s_lock); +} + +int replica_add_by_dn (const char *dn) +{ + char *dn_copy = NULL; + + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* make sure that the dn is unique */ + if (PL_HashTableLookup(s_hash, dn) != NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: " + "replica with dn (%s) already in the hash\n", dn); + PR_RWLock_Unlock (s_lock); + return -1 ; + } + + /* add dn */ + dn_copy = slapi_ch_strdup(dn); + if (PL_HashTableAdd(s_hash, dn_copy, dn_copy) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_dn: " + "failed to add dn (%s); NSPR error - %d\n", + dn_copy, PR_GetError ()); + slapi_ch_free((void **)&dn_copy); + PR_RWLock_Unlock (s_lock); + return -1; + } + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_add_by_dn: " + "added dn (%s)\n", + dn_copy); + PR_RWLock_Unlock (s_lock); + return 0; +} + +int replica_delete_by_dn (const char *dn) +{ + char *dn_copy = NULL; + + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: " + "NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* locate object */ + if (NULL == (dn_copy = (char *)PL_HashTableLookup(s_hash, dn))) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_dn: " + "dn (%s) is not in the hash.\n", dn); + PR_RWLock_Unlock (s_lock); + return -1; + } + + /* remove from hash */ + PL_HashTableRemove(s_hash, dn); + slapi_ch_free((void **)&dn_copy); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_delete_by_dn: " + "removed dn (%s)\n", + dn); + PR_RWLock_Unlock (s_lock); + + return 0; +} + +int replica_is_being_configured (const char *dn) +{ + if (dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_is_dn_in_hash: " + "NULL argument\n"); + return 0; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_is_dn_in_hash: " + "dn hash is not initialized\n"); + return 0; + } + + PR_RWLock_Wlock (s_lock); + + /* locate object */ + if (NULL == PL_HashTableLookup(s_hash, dn)) + { + PR_RWLock_Unlock (s_lock); + return 0; + } + + PR_RWLock_Unlock (s_lock); + + return 1; +} + +/* Helper functions */ + +/* this function called for each hash node during hash destruction */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg) +{ + char *dn_copy; + + if (he == NULL) + return HT_ENUMERATE_NEXT; + + dn_copy = (char*)he->value; + slapi_ch_free((void **)&dn_copy); + + return HT_ENUMERATE_REMOVE; +} diff --git a/ldap/servers/plugins/replication/repl5_replica_hash.c b/ldap/servers/plugins/replication/repl5_replica_hash.c new file mode 100644 index 00000000..92ea87f4 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replica_hash.c @@ -0,0 +1,243 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_replica_hash.c */ + +#include "repl5.h" +#include "plhash.h" + +/* global data */ +static PLHashTable *s_hash; +static PRRWLock *s_lock; + +struct repl_enum_data +{ + FNEnumReplica fn; + void *arg; +}; + +/* Forward declarations */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg); +static PRIntn replica_enumerate (PLHashEntry *he, PRIntn index, void *hash_data); + + +int replica_init_name_hash () +{ + /* allocate table */ + s_hash = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, + PL_CompareValues, NULL, NULL); + if (s_hash == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_name_hash: " + "failed to allocate hash table; NSPR error - %d\n", + PR_GetError ()); + return -1; + } + + /* create lock */ + s_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "replica_hash_lock"); + if (s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_init_name_hash: " + "failed to create lock; NSPR error - %d\n", + PR_GetError ()); + replica_destroy_name_hash (); + return -1; + } + + return 0; +} + +void replica_destroy_name_hash () +{ + /* destroy the content */ + PL_HashTableEnumerateEntries(s_hash, replica_destroy_hash_entry, NULL); + + if (s_hash) + PL_HashTableDestroy(s_hash); + + if (s_lock) + PR_DestroyRWLock (s_lock); +} + +int replica_add_by_name (const char *name, Object *replica) +{ + if (name == NULL || replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* make sure that the name is unique */ + if (PL_HashTableLookup(s_hash, name) != NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: " + "replica with name (%s) already in the hash\n", name); + PR_RWLock_Unlock (s_lock); + return -1 ; + } + + /* acquire replica object */ + object_acquire (replica); + + /* add replica */ + if (PL_HashTableAdd(s_hash, name, replica) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_add_by_name: " + "failed to add replica with name (%s); NSPR error - %d\n", + name, PR_GetError ()); + object_release (replica); + PR_RWLock_Unlock (s_lock); + return -1; + } + + PR_RWLock_Unlock (s_lock); + return 0; +} + +int replica_delete_by_name (const char *name) +{ + Object *replica; + + if (name == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: " + "NULL argument\n"); + return -1; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: " + "replica hash is not initialized\n"); + return -1; + } + + PR_RWLock_Wlock (s_lock); + + /* locate object */ + replica = (Object*)PL_HashTableLookup(s_hash, name); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_delete_by_name: " + "replica with name (%s) is not in the hash.\n", name); + PR_RWLock_Unlock (s_lock); + return -1; + } + + /* remove from hash */ + PL_HashTableRemove(s_hash, name); + + /* release replica */ + object_release (replica); + + PR_RWLock_Unlock (s_lock); + + return 0; +} + +Object* replica_get_by_name (const char *name) +{ + Object *replica; + + if (name == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_get_by_name: " + "NULL argument\n"); + return NULL; + } + + if (s_hash == NULL || s_lock == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_get_by_name: " + "replica hash is not initialized\n"); + return NULL; + } + + PR_RWLock_Rlock (s_lock); + + /* locate object */ + replica = (Object*)PL_HashTableLookup(s_hash, name); + if (replica == NULL) + { + PR_RWLock_Unlock (s_lock); + return NULL; + } + + object_acquire (replica); + + PR_RWLock_Unlock (s_lock); + + return replica; +} + +void replica_enumerate_replicas (FNEnumReplica fn, void *arg) +{ + struct repl_enum_data data; + + PR_ASSERT (fn); + + data.fn = fn; + data.arg = arg; + + PR_RWLock_Wlock (s_lock); + PL_HashTableEnumerateEntries(s_hash, replica_enumerate, &data); + PR_RWLock_Unlock (s_lock); +} + +/* Helper functions */ + +/* this function called for each hash node during hash destruction */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg) +{ + Object *r_obj; + Replica *r; + + if (he == NULL) + return HT_ENUMERATE_NEXT; + + r_obj = (Object*)he->value; + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + /* flash replica state to the disk */ + replica_flush (r); + + /* release replica object */ + object_release (r_obj); + + return HT_ENUMERATE_REMOVE; +} + +static PRIntn replica_enumerate (PLHashEntry *he, PRIntn index, void *hash_data) +{ + Object *r_obj; + Replica *r; + struct repl_enum_data *data = hash_data; + + r_obj = (Object*)he->value; + PR_ASSERT (r_obj); + + object_acquire (r_obj); + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + data->fn (r, data->arg); + + object_release (r_obj); + + return HT_ENUMERATE_NEXT; +} + diff --git a/ldap/servers/plugins/replication/repl5_replsupplier.c b/ldap/servers/plugins/replication/repl5_replsupplier.c new file mode 100644 index 00000000..e98d0e58 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_replsupplier.c @@ -0,0 +1,166 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_replsupplier.c */ +/* + +A replsupplier is an object that knows how to manage outbound replication +for one consumer. + +Methods: +init() +configure() +start() +stop() +destroy() +status() +notify() + +*/ + +#include "slapi-plugin.h" +#include "repl5.h" + +typedef struct repl_supplier { + PRUint32 client_change_count; /* # of client-supplied changes */ + PRUint32 repl_change_count; /* # of replication updates */ + + PRLock *lock; + +} repl_supplier; + + +static void repl_supplier_free(Repl_Supplier **rsp); + +/* + * Create and initialize this replsupplier object. + */ +Repl_Supplier * +replsupplier_init(Slapi_Entry *e) +{ + Repl_Supplier *rs; + + if ((rs = (Repl_Supplier *)slapi_ch_malloc(sizeof(Repl_Supplier))) == NULL) + { + goto loser; + } + if ((rs->lock = PR_NewLock()) == NULL) + { + goto loser; + } + return rs; + +loser: + repl_supplier_free(&rs); + return NULL; +} + + + +static void +repl_supplier_free(Repl_Supplier **rsp) +{ + if (NULL != rsp) + { + Repl_Supplier *rs = *rsp; + if (NULL != rs) + { + if (NULL != rs->lock) + { + PR_DestroyLock(rs->lock); + rs->lock = NULL; + } + slapi_ch_free((void **)rsp); + } + } +} + + + +/* + * Configure a repl_supplier object. + */ +void +replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb) +{ + PR_ASSERT(NULL != rs); + +} + + + +/* + * Start a repl_supplier object. This means that it's ok for + * the repl_supplier to begin normal replication duties. It does + * not necessarily mean that a replication session will occur + * immediately. + */ +void +replsupplier_start(Repl_Supplier *rs) +{ + PR_ASSERT(NULL != rs); +} + + + + +/* + * Stop a repl_supplier object. This causes any active replication + * sessions to be stopped ASAP, and puts the repl_supplier into a + * stopped state. No additional replication activity will occur + * until the replsupplier_start() function is called. + */ +void +replsupplier_stop(Repl_Supplier *rs) +{ + PR_ASSERT(NULL != rs); +} + + + + +/* + * Destroy a repl_supplier object. The object will be stopped, if it + * is not already stopped. + */ +void +replsupplier_destroy(Repl_Supplier **rsp) +{ + Repl_Supplier *rs; + + PR_ASSERT(NULL != rsp && NULL != *rsp); + + rs = *rsp; + + slapi_ch_free((void **)rsp); +} + + + +/* + * This method should be called by the repl_bos whenever it determines + * that a change to the replicated area serviced by this repl_supplier + * has occurred. This gives the repl_supplier a chance to implement a + * scheduling policy. + */ +void +replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask) +{ + PR_ASSERT(NULL != rs); +} + + + +/* + * This method is used to obtain the status of this particular + * repl_supplier object. Eventually it will return some object containing + * status information. For now, it's just a placeholder function. + */ +PRUint32 +replsupplier_get_status(Repl_Supplier *rs) +{ + PR_ASSERT(NULL != rs); + return 0; +} diff --git a/ldap/servers/plugins/replication/repl5_ruv.c b/ldap/servers/plugins/replication/repl5_ruv.c new file mode 100644 index 00000000..b8cb79c9 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_ruv.c @@ -0,0 +1,2022 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_ruv.c - implementation of replica update vector */ +/* + * The replica update vector is stored in the nsds50ruv attribute. The LDIF + * representation of the ruv is: + * nsds50ruv: {replicageneration} <gen-id-for-this-replica> + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>] + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>] + * ... + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn>] + * + * nsruvReplicaLastModified: {replica <rid>[ <url>]} lastModifiedTime + * nsruvReplicaLastModified: {replica <rid>[ <url>]} lastModifiedTime + * ... + * + * For readability, ruv_dump appends nsruvReplicaLastModified to nsds50ruv: + * nsds50ruv: {replica <rid>[ <url>]}[ <mincsn> <maxcsn> [<lastModifiedTime>]] + */ + +#include <string.h> +#include <ctype.h> /* For isdigit() */ +#include "csnpl.h" +#include "repl5_ruv.h" +#include "repl_shared.h" +#include "repl5.h" + +#define RIDSTR_SIZE 16 /* string large enough to hold replicaid*/ +#define RUVSTR_SIZE 256 /* string large enough to hold ruv and lastmodifiedtime */ + +/* Data Structures */ + +/* replica */ +typedef struct ruvElement +{ + ReplicaId rid; /* replica id for this element */ + CSN *csn; /* largest csn that we know about that originated at the master */ + CSN *min_csn; /* smallest csn that originated at the master */ + char *replica_purl; /* Partial URL for replica */ + CSNPL *csnpl; /* list of operations in progress */ + time_t last_modified; /* timestamp the modification of csn */ +} RUVElement; + +/* replica update vector */ +struct _ruv +{ + char *replGen; /* replicated area generation: identifies replica + in space and in time */ + DataList *elements; /* replicas */ + PRRWLock *lock; /* concurrency control */ +}; + +/* forward declarations */ +static int ruvInit (RUV **ruv, int initCount); +static void ruvFreeReplica (void **data); +static RUVElement* ruvGetReplica (const RUV *ruv, ReplicaId rid); +static RUVElement* ruvAddReplica (RUV *ruv, const CSN *csn, const char *replica_purl); +static RUVElement* ruvAddReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl); +static RUVElement* ruvAddIndexReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl, int index); +static int ruvReplicaCompare (const void *el1, const void *el2); +static RUVElement *get_ruvelement_from_berval(const struct berval *bval); +static char *get_replgen_from_berval(const struct berval *bval); + +static const char * const prefix_replicageneration = "{replicageneration}"; +static const char * const prefix_ruvcsn = "{replica "; /* intentionally missing '}' */ + + +/* API implementation */ + + +/* + * Allocate a new ruv and set its replica generation to the given generation. + */ +int +ruv_init_new(const char *replGen, ReplicaId rid, const char *purl, RUV **ruv) +{ + int rc; + RUVElement *replica; + + if (ruv == NULL || replGen == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_init_new: NULL argument\n"); + return RUV_BAD_DATA; + } + + rc = ruvInit (ruv, 0); + if (rc != RUV_SUCCESS) + return rc; + + (*ruv)->replGen = slapi_ch_strdup (replGen); + + /* we want to add the local writable replica to the RUV before any csns are created */ + /* this is so that it can be referred to even before it accepted any changes */ + if (purl) + { + replica = ruvAddReplicaNoCSN (*ruv, rid, purl); + + if (replica == NULL) + return RUV_MEMORY_ERROR; + } + + return RUV_SUCCESS; +} + + +/* + * Create a new RUV and initialize its contents from the provided Slapi_Attr. + * Returns: + * RUV_BAD_DATA if the values in the attribute were malformed. + * RUV_SUCCESS if all went well + */ +int +ruv_init_from_slapi_attr(Slapi_Attr *attr, RUV **ruv) +{ + ReplicaId dummy = 0; + + return (ruv_init_from_slapi_attr_and_check_purl(attr, ruv, &dummy)); +} + +/* + * Create a new RUV and initialize its contents from the provided Slapi_Attr. + * Returns: + * RUV_BAD_DATA if the values in the attribute were malformed. + * RUV_SUCCESS if all went well + * contain_purl is 0 if the ruv doesn't contain the local purl + * contain_purl is != 0 if the ruv contains the local purl (contains the rid) + */ +int +ruv_init_from_slapi_attr_and_check_purl(Slapi_Attr *attr, RUV **ruv, ReplicaId *contain_purl) +{ + int return_value; + + PR_ASSERT(NULL != attr && NULL != ruv); + + if (NULL == ruv || NULL == attr) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_attr: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + int rc; + int numvalues; + slapi_attr_get_numvalues(attr, &numvalues); + if ((rc = ruvInit(ruv, numvalues)) != RUV_SUCCESS) + { + return_value = rc; + } + else + { + int hint; + Slapi_Value *value; + const struct berval *bval; + const char *purl = NULL; + + return_value = RUV_SUCCESS; + + purl = multimaster_get_local_purl(); + *contain_purl = 0; + + for (hint = slapi_attr_first_value(attr, &value); + hint != -1; hint = slapi_attr_next_value(attr, hint, &value)) + { + bval = slapi_value_get_berval(value); + if (NULL != bval && NULL != bval->bv_val) + { + if (strncmp(bval->bv_val, prefix_replicageneration, strlen(prefix_replicageneration)) == 0) { + if (NULL == (*ruv)->replGen) + { + (*ruv)->replGen = get_replgen_from_berval(bval); + } else { + /* Twice replicageneration is wrong, just log and ignore */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_attr: %s is present more than once\n", + prefix_replicageneration); + } + } + else + { + RUVElement *ruve = get_ruvelement_from_berval(bval); + if (NULL != ruve) + { + /* Is the local purl already in the ruv ? */ + if ( (*contain_purl==0) && (strncmp(ruve->replica_purl, purl, strlen(purl))==0) ) + { + *contain_purl = ruve->rid; + } + dl_add ((*ruv)->elements, ruve); + } + } + } + } + } + } + return return_value; +} + + + +/* + * Same as ruv_init_from_slapi_attr, but takes an array of pointers to bervals. + * I wish this wasn't a cut-n-paste of the above function, but I don't see a + * clean way to define one API in terms of the other. + */ +int +ruv_init_from_bervals(struct berval **vals, RUV **ruv) +{ + int return_value; + + PR_ASSERT(NULL != vals && NULL != ruv); + + if (NULL == ruv || NULL == vals) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_value: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + int i, rc; + i = 0; + while (vals[i] != NULL) + { + i++; + } + if ((rc = ruvInit (ruv, i)) != RUV_SUCCESS) + { + return_value = rc; + } + else + { + return_value = RUV_SUCCESS; + for (i = 0; NULL != vals[i]; i++) + { + if (NULL != vals[i]->bv_val) + { + if (strncmp(vals[i]->bv_val, prefix_replicageneration, strlen(prefix_replicageneration)) == 0) { + if (NULL == (*ruv)->replGen) + { + (*ruv)->replGen = get_replgen_from_berval(vals[i]); + } else { + /* Twice replicageneration is wrong, just log and ignore */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_init_from_slapi_value: %s is present more than once\n", + prefix_replicageneration); + } + } + else + { + RUVElement *ruve = get_ruvelement_from_berval(vals[i]); + if (NULL != ruve) + { + dl_add ((*ruv)->elements, ruve); + } + } + } + } + } + } + return return_value; +} + + + +RUV* +ruv_dup (const RUV *ruv) +{ + int rc; + RUVElement *replica, *dupReplica; + int cookie; + RUV *dupRUV = NULL; + + if (ruv == NULL) + return NULL; + + PR_RWLock_Rlock (ruv->lock); + + rc = ruvInit (&dupRUV, dl_get_count (ruv->elements)); + if (rc != RUV_SUCCESS || dupRUV == NULL) + goto done; + + dupRUV->replGen = slapi_ch_strdup (ruv->replGen); + + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + dupReplica = (RUVElement *)slapi_ch_calloc (1, sizeof (*dupReplica)); + dupReplica->rid = replica->rid; + if (replica->csn) + dupReplica->csn = csn_dup (replica->csn); + if (replica->min_csn) + dupReplica->min_csn = csn_dup (replica->min_csn); + if (replica->replica_purl) + dupReplica->replica_purl = slapi_ch_strdup (replica->replica_purl); + dupReplica->last_modified = replica->last_modified; + + /* ONREPL - we don't make copy of the pernding list. For now + we don't need it. */ + + dl_add (dupRUV->elements, dupReplica); + } + +done: + PR_RWLock_Unlock (ruv->lock); + + return dupRUV; +} + +void +ruv_destroy (RUV **ruv) +{ + if (ruv != NULL && *ruv != NULL) + { + if ((*ruv)->elements) + { + dl_cleanup ((*ruv)->elements, ruvFreeReplica); + dl_free (&(*ruv)->elements); + } + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void **)&((*ruv)->replGen)); + + if ((*ruv)->lock) + { + PR_DestroyRWLock ((*ruv)->lock); + } + + slapi_ch_free ((void**)ruv); + } +} + +/* + * [610948] + * copy elements in srcruv to destruv + * destruv is the live wrapper, which could be referred by other threads. + * srcruv is cleaned up after copied. + */ +void +ruv_copy_and_destroy (RUV **srcruv, RUV **destruv) +{ + DataList *elemp = NULL; + char *replgp = NULL; + + if (NULL == srcruv || NULL == *srcruv || NULL == destruv) + { + return; + } + + if (NULL == *destruv) + { + *destruv = *srcruv; + *srcruv = NULL; + } + else + { + PR_RWLock_Wlock((*destruv)->lock); + elemp = (*destruv)->elements; + (*destruv)->elements = (*srcruv)->elements; + if (elemp) + { + dl_cleanup (elemp, ruvFreeReplica); + dl_free (&elemp); + } + + /* slapi_ch_free accepts NULL pointer */ + replgp = (*destruv)->replGen; + (*destruv)->replGen = (*srcruv)->replGen; + slapi_ch_free ((void **)&replgp); + + if ((*srcruv)->lock) + { + PR_DestroyRWLock ((*srcruv)->lock); + } + slapi_ch_free ((void**)srcruv); + + PR_RWLock_Unlock((*destruv)->lock); + } + PR_ASSERT (*destruv != NULL && *srcruv == NULL); +} + +int +ruv_delete_replica (RUV *ruv, ReplicaId rid) +{ + int return_value; + if (ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_delete_replica: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + /* check for duplicates */ + PR_RWLock_Wlock (ruv->lock); + dl_delete (ruv->elements, (const void*)&rid, ruvReplicaCompare, ruvFreeReplica); + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_add_replica (RUV *ruv, ReplicaId rid, const char *replica_purl) +{ + RUVElement* replica; + + PR_ASSERT (ruv && replica_purl); + + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) + { + replica = ruvAddReplicaNoCSN (ruv, rid, replica_purl); + } + + PR_RWLock_Unlock (ruv->lock); + + if (replica) + return RUV_SUCCESS; + else + return RUV_MEMORY_ERROR; +} + +int +ruv_replace_replica_purl (RUV *ruv, ReplicaId rid, const char *replica_purl) +{ + RUVElement* replica; + int rc = RUV_NOTFOUND; + + PR_ASSERT (ruv && replica_purl); + + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + if (replica != NULL) + { + slapi_ch_free((void **)&(replica->replica_purl)); + replica->replica_purl = slapi_ch_strdup(replica_purl); + rc = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int +ruv_add_index_replica (RUV *ruv, ReplicaId rid, const char *replica_purl, int index) +{ + RUVElement* replica; + + PR_ASSERT (ruv && replica_purl); + + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) + { + replica = ruvAddIndexReplicaNoCSN (ruv, rid, replica_purl, index); + } + + PR_RWLock_Unlock (ruv->lock); + + if (replica) + return RUV_SUCCESS; + else + return RUV_MEMORY_ERROR; +} + + +PRBool +ruv_contains_replica (const RUV *ruv, ReplicaId rid) +{ + RUVElement *replica; + + if (ruv == NULL) + return PR_FALSE; + + PR_RWLock_Rlock (ruv->lock); + replica = ruvGetReplica (ruv, rid); + PR_RWLock_Unlock (ruv->lock); + + return replica != NULL; +} + + + + +#define GET_LARGEST_CSN 231 +#define GET_SMALLEST_CSN 232 +static int +get_csn_internal(const RUV *ruv, ReplicaId rid, CSN **csn, int whichone) +{ + RUVElement *replica; + int return_value = RUV_SUCCESS; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_get_largest_csn_for_replica: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + *csn = NULL; + /* prevent element from being destroyed while we get its data */ + PR_RWLock_Rlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + /* replica without min csn is treated as a non-existent replica */ + if (replica == NULL || replica->min_csn == NULL) + { + return_value = RUV_NOTFOUND; + } + else + { + switch (whichone) + { + case GET_LARGEST_CSN: + *csn = replica->csn ? csn_dup (replica->csn) : NULL; + break; + case GET_SMALLEST_CSN: + *csn = replica->min_csn ? csn_dup (replica->min_csn) : NULL; + break; + default: + *csn = NULL; + } + } + PR_RWLock_Unlock (ruv->lock); + } + return return_value; +} + + +int +ruv_get_largest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn) +{ + return get_csn_internal(ruv, rid, csn, GET_LARGEST_CSN); +} + +int +ruv_get_smallest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn) +{ + return get_csn_internal(ruv, rid, csn, GET_SMALLEST_CSN); +} + +const char * +ruv_get_purl_for_replica(const RUV *ruv, ReplicaId rid) +{ + RUVElement *replica; + const char *return_value = NULL; + + PR_RWLock_Rlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + if (replica != NULL) + { + return_value = replica->replica_purl; + } + + PR_RWLock_Unlock (ruv->lock); + + return return_value; +} + + +static int +set_min_csn_nolock(RUV *ruv, const CSN *min_csn, const char *replica_purl) +{ + int return_value; + ReplicaId rid = csn_get_replicaid (min_csn); + RUVElement *replica = ruvGetReplica (ruv, rid); + if (NULL == replica) + { + replica = ruvAddReplica (ruv, min_csn, replica_purl); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (replica->min_csn == NULL || csn_compare (min_csn, replica->min_csn) < 0) + { + csn_free(&replica->min_csn); + replica->min_csn = csn_dup(min_csn); + } + + return_value = RUV_SUCCESS; + } + + return return_value; +} + +static int +set_max_csn_nolock(RUV *ruv, const CSN *max_csn, const char *replica_purl) +{ + int return_value; + ReplicaId rid = csn_get_replicaid (max_csn); + RUVElement *replica = ruvGetReplica (ruv, rid); + if (NULL == replica) + { + replica = ruvAddReplica (ruv, max_csn, replica_purl); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (replica_purl && replica->replica_purl == NULL) + replica->replica_purl = slapi_ch_strdup (replica_purl); + csn_free(&replica->csn); + replica->csn = csn_dup(max_csn); + replica->last_modified = current_time(); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_set_min_csn(RUV *ruv, const CSN *min_csn, const char *replica_purl) +{ + int return_value; + PR_RWLock_Wlock (ruv->lock); + return_value = set_min_csn_nolock(ruv, min_csn, replica_purl); + PR_RWLock_Unlock (ruv->lock); + return return_value; +} + + +int +ruv_set_max_csn(RUV *ruv, const CSN *max_csn, const char *replica_purl) +{ + int return_value; + PR_RWLock_Wlock (ruv->lock); + return_value = set_max_csn_nolock(ruv, max_csn, replica_purl); + PR_RWLock_Unlock (ruv->lock); + return return_value; +} + +int +ruv_set_csns(RUV *ruv, const CSN *csn, const char *replica_purl) +{ + RUVElement *replica; + ReplicaId rid; + int return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_set_csns: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + rid = csn_get_replicaid (csn); + + /* prevent element from being destroyed while we get its data */ + PR_RWLock_Wlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) /* add new replica */ + { + replica = ruvAddReplica (ruv, csn, replica_purl); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (csn_compare (csn, replica->csn) > 0) + { + if (replica->csn != NULL) + { + csn_init_by_csn ( replica->csn, csn ); + } + else + { + replica->csn = csn_dup(csn); + } + replica->last_modified = current_time(); + if (replica_purl && (NULL == replica->replica_purl || + strcmp(replica->replica_purl, replica_purl) != 0)) + { + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&replica->replica_purl); + + replica->replica_purl = slapi_ch_strdup(replica_purl); + } + } + /* XXXggood only need to worry about this if real min csn not committed to changelog yet */ + if (csn_compare (csn, replica->min_csn) < 0) + { + csn_free(&replica->min_csn); + replica->min_csn = csn_dup(csn); + } + return_value = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + } + return return_value; +} + +/* This function, for each replica keeps the smallest CSN its seen so far. + Used for initial setup of changelog purge vector */ + +int +ruv_set_csns_keep_smallest(RUV *ruv, const CSN *csn) +{ + RUVElement *replica; + ReplicaId rid; + int return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruv_set_csns_keep_smallest: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + rid = csn_get_replicaid (csn); + + /* prevent element from being destroyed while we get its data */ + PR_RWLock_Wlock (ruv->lock); + + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) /* add new replica */ + { + replica = ruvAddReplica (ruv, csn, NULL); + if (replica) + return_value = RUV_SUCCESS; + else + return_value = RUV_MEMORY_ERROR; + } + else + { + if (csn_compare (csn, replica->csn) < 0) + { + csn_free(&replica->csn); + replica->csn = csn_dup(csn); + replica->last_modified = current_time(); + } + + return_value = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + } + return return_value; +} + + +void +ruv_set_replica_generation(RUV *ruv, const char *csnstr) +{ + if (NULL != csnstr && NULL != ruv) + { + PR_RWLock_Wlock (ruv->lock); + + if (NULL != ruv->replGen) + { + slapi_ch_free((void **)&ruv->replGen); + } + ruv->replGen = slapi_ch_strdup(csnstr); + + PR_RWLock_Unlock (ruv->lock); + } +} + + +char * +ruv_get_replica_generation(const RUV *ruv) +{ + char *return_str = NULL; + + PR_RWLock_Rlock (ruv->lock); + + if (ruv != NULL && ruv->replGen != NULL) + { + return_str = slapi_ch_strdup(ruv->replGen); + } + + PR_RWLock_Unlock (ruv->lock); + + return return_str; +} + +static PRBool +ruv_covers_csn_internal(const RUV *ruv, const CSN *csn, PRBool strict) +{ + RUVElement *replica; + ReplicaId rid; + PRBool return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_covers_csn: NULL argument\n"); + return_value = PR_FALSE; + } + else + { + rid = csn_get_replicaid(csn); + replica = ruvGetReplica (ruv, rid); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_covers_csn: replica for id %d not found\n", rid); + return_value = PR_FALSE; + } + else + { + if (strict) + { + return_value = (csn_compare (csn, replica->csn) < 0); + } + else + { + return_value = (csn_compare (csn, replica->csn) <= 0); + } + } + } + return return_value; +} + +PRBool +ruv_covers_csn(const RUV *ruv, const CSN *csn) +{ + PRBool rc; + + PR_RWLock_Rlock (ruv->lock); + rc = ruv_covers_csn_internal(ruv, csn, PR_FALSE); + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + +PRBool +ruv_covers_csn_strict(const RUV *ruv, const CSN *csn) +{ + PRBool rc; + + PR_RWLock_Rlock (ruv->lock); + rc = ruv_covers_csn_internal(ruv, csn, PR_TRUE); + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + + +/* + * The function gets min{maxcsns of all ruv elements} if get_the_max=0, + * or max{maxcsns of all ruv elements} if get_the_max != 0. + */ +static int +ruv_get_min_or_max_csn(const RUV *ruv, CSN **csn, int get_the_max) +{ + int return_value; + + if (ruv == NULL || csn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_get_min_or_max_csn: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + CSN *found = NULL; + RUVElement *replica; + int cookie; + PR_RWLock_Rlock (ruv->lock); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + /* + * Skip replica whose maxcsn is NULL otherwise + * the code will return different min_csn if + * the sequence of the replicas is altered. + * + * don't use READ_ONLY replicas for computing the value of + * "found", as they seem to have NULL csn and min_csn + */ + if (replica->csn == NULL || replica->rid == READ_ONLY_REPLICA_ID) + { + continue; + } + + if (found == NULL || + (!get_the_max && csn_compare(found, replica->csn)>0) || + ( get_the_max && csn_compare(found, replica->csn)<0)) + { + found = replica->csn; + } + } + if (found == NULL) + { + *csn = NULL; + } + else + { + *csn = csn_dup (found); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_get_max_csn(const RUV *ruv, CSN **csn) +{ + return ruv_get_min_or_max_csn(ruv, csn, 1 /* get the max */); +} + +int +ruv_get_min_csn(const RUV *ruv, CSN **csn) +{ + return ruv_get_min_or_max_csn(ruv, csn, 0 /* get the min */); +} + +int +ruv_enumerate_elements (const RUV *ruv, FNEnumRUV fn, void *arg) +{ + int cookie; + RUVElement *elem; + int rc = 0; + ruv_enum_data enum_data = {0}; + + if (ruv == NULL || fn == NULL) + { + /* ONREPL - log error */ + return -1; + } + + PR_RWLock_Rlock (ruv->lock); + for (elem = (RUVElement*)dl_get_first (ruv->elements, &cookie); elem; + elem = (RUVElement*)dl_get_next (ruv->elements, &cookie)) + { + /* we only return elements that contains both minimal and maximal CSNs */ + if (elem->csn && elem->min_csn) + { + enum_data.csn = elem->csn; + enum_data.min_csn = elem->min_csn; + rc = fn (&enum_data, arg); + if (rc != 0) + break; + } + } + + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + +/* + * Convert a replica update vector to a NULL-terminated array + * of bervals. The caller is responsible for freeing the bervals. + */ +int +ruv_to_bervals(const RUV *ruv, struct berval ***bvals) +{ + struct berval **returned_bervals = NULL; + int return_value; + if (ruv == NULL || bvals == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_to_bervals: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + int count; + int i; + RUVElement *replica; + char csnStr1 [CSN_STRSIZE]; + char csnStr2 [CSN_STRSIZE]; + int cookie; + PR_RWLock_Rlock (ruv->lock); + count = dl_get_count (ruv->elements) + 2; + returned_bervals = (struct berval **)slapi_ch_malloc(sizeof(struct berval *) * count); + returned_bervals[count - 1] = NULL; + returned_bervals[0] = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + returned_bervals[0]->bv_val = slapi_ch_malloc(strlen(prefix_replicageneration) + strlen(ruv->replGen) + 2); + sprintf(returned_bervals[0]->bv_val, "%s %s", + prefix_replicageneration, ruv->replGen); + returned_bervals[0]->bv_len = strlen(returned_bervals[0]->bv_val); + for (i = 1, replica = dl_get_first (ruv->elements, &cookie); replica; + i++, replica = dl_get_next (ruv->elements, &cookie)) + { + returned_bervals[i] = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + returned_bervals[i]->bv_val = slapi_ch_malloc(strlen(prefix_ruvcsn) + RIDSTR_SIZE + + ((replica->replica_purl == NULL) ? 0 : strlen(replica->replica_purl)) + + ((replica->min_csn == NULL) ? 0 : CSN_STRSIZE) + + ((replica->csn == NULL) ? 0 : CSN_STRSIZE) + 5); + sprintf(returned_bervals[i]->bv_val, "%s%d%s%s}%s%s%s%s", + prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + replica->min_csn == NULL ? "" : " ", + replica->min_csn == NULL ? "" : csn_as_string (replica->min_csn, PR_FALSE, csnStr1), + replica->csn == NULL ? "" : " ", + replica->csn == NULL ? "" : csn_as_string (replica->csn, PR_FALSE, csnStr2)); + returned_bervals[i]->bv_len = strlen(returned_bervals[i]->bv_val); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + *bvals = returned_bervals; + } + return return_value; +} + +int +ruv_to_smod(const RUV *ruv, Slapi_Mod *smod) +{ + int return_value; + + if (ruv == NULL || smod == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_to_smod: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + struct berval val; + RUVElement *replica; + int cookie; + char csnStr1 [CSN_STRSIZE]; + char csnStr2 [CSN_STRSIZE]; +#define B_SIZ 1024 + char buf[B_SIZ]; + PR_RWLock_Rlock (ruv->lock); + slapi_mod_init (smod, dl_get_count (ruv->elements) + 1); + slapi_mod_set_type (smod, type_ruvElement); + slapi_mod_set_operation (smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + PR_snprintf(buf, B_SIZ, "%s %s", prefix_replicageneration, ruv->replGen); + val.bv_val = buf; + val.bv_len = strlen(buf); + slapi_mod_add_value(smod, &val); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + + PR_snprintf(buf, B_SIZ, "%s%d%s%s}%s%s%s%s", prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + replica->min_csn == NULL ? "" : " ", + replica->min_csn == NULL ? "" : csn_as_string (replica->min_csn, PR_FALSE, csnStr1), + replica->csn == NULL ? "" : " ", + replica->csn == NULL ? "" : csn_as_string (replica->csn, PR_FALSE, csnStr2)); + val.bv_len = strlen(buf); + slapi_mod_add_value(smod, &val); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +int +ruv_last_modified_to_smod(const RUV *ruv, Slapi_Mod *smod) +{ + int return_value; + + if (ruv == NULL || smod == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_last_modified_to_smod: NULL argument\n"); + return_value = RUV_BAD_DATA; + } + else + { + struct berval val; + RUVElement *replica; + int cookie; + char buf[B_SIZ]; + PR_RWLock_Rlock (ruv->lock); + slapi_mod_init (smod, dl_get_count (ruv->elements)); + slapi_mod_set_type (smod, type_ruvElementUpdatetime); + slapi_mod_set_operation (smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + val.bv_val = buf; + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + PR_snprintf(buf, B_SIZ, "%s%d%s%s} %08lx", prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + replica->last_modified); + val.bv_len = strlen(buf); + slapi_mod_add_value(smod, &val); + } + PR_RWLock_Unlock (ruv->lock); + return_value = RUV_SUCCESS; + } + return return_value; +} + +/* + * XXXggood do we need "ruv_covers_ruv_strict" ???? */ +PRBool +ruv_covers_ruv(const RUV *covering_ruv, const RUV *covered_ruv) +{ + PRBool return_value = PR_TRUE; + RUVElement *replica; + int cookie; + + /* compare replica generations first */ + if (covering_ruv->replGen == NULL) + { + if (covered_ruv->replGen) + return PR_FALSE; + } + else + { + if (covered_ruv->replGen == NULL) + return PR_FALSE; + } + + if (strcasecmp (covered_ruv->replGen, covering_ruv->replGen)) + return PR_FALSE; + + /* replica generation is the same, now compare element by element */ + for (replica = dl_get_first (covered_ruv->elements, &cookie); + NULL != replica; + replica = dl_get_next (covered_ruv->elements, &cookie)) + { + if (replica->csn && + (ruv_covers_csn(covering_ruv, replica->csn) == PR_FALSE)) + { + return_value = PR_FALSE; + /* Don't break here - may leave something referenced? */ + } + } + return return_value; +} + +PRInt32 +ruv_replica_count (const RUV *ruv) +{ + if (ruv == NULL) + return 0; + else + { + int count; + + PR_RWLock_Rlock (ruv->lock); + count = dl_get_count (ruv->elements); + PR_RWLock_Unlock (ruv->lock); + + return count; + } +} + +/* + * Extract all the referral URL's from the RUV (but self URL), + * returning them in an array of strings, that + * the caller must free. + */ +char ** +ruv_get_referrals(const RUV *ruv) +{ + char **r= NULL; + int n; + const char *mypurl = multimaster_get_local_purl(); + + PR_RWLock_Rlock (ruv->lock); + + n = ruv_replica_count(ruv); + if(n>0) + { + RUVElement *replica; + int cookie; + int i= 0; + r= (char**)slapi_ch_calloc(sizeof(char*),n+1); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + /* Add URL into referrals if doesn't match self URL */ + if((replica->replica_purl!=NULL) && + (slapi_utf8casecmp((unsigned char *)replica->replica_purl, + (unsigned char *)mypurl) != 0)) + { + r[i]= slapi_ch_strdup(replica->replica_purl); + i++; + } + } + } + + PR_RWLock_Unlock (ruv->lock); + + return r; /* Caller must free this */ +} + +void +ruv_dump(const RUV *ruv, char *ruv_name, PRFileDesc *prFile) +{ + RUVElement *replica; + int cookie; + char csnstr1[CSN_STRSIZE]; + char csnstr2[CSN_STRSIZE]; + char buff[RUVSTR_SIZE]; + int len = sizeof (buff); + + PR_ASSERT(NULL != ruv); + + PR_RWLock_Rlock (ruv->lock); + + PR_snprintf (buff, len, "%s: {replicageneration} %s\n", + ruv_name ? ruv_name : type_ruvElement, + ruv->replGen == NULL ? "" : ruv->replGen); + + if (prFile) + { + slapi_write_buffer (prFile, buff, strlen(buff)); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, buff); + } + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + /* prefix_ruvcsn = "{replica " */ + PR_snprintf (buff, len, "%s: %s%d%s%s} %s %s\n", + ruv_name ? ruv_name : type_ruvElement, + prefix_ruvcsn, replica->rid, + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + csn_as_string(replica->min_csn, PR_FALSE, csnstr1), + csn_as_string(replica->csn, PR_FALSE, csnstr2)); + if (strlen (csnstr1) > 0) { + PR_snprintf (buff + strlen(buff) - 1, len - strlen(buff), " %08lx\n", + replica->last_modified); + } + if (prFile) + { + slapi_write_buffer (prFile, buff, strlen(buff)); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, buff); + } + } + + PR_RWLock_Unlock (ruv->lock); +} + +/* this function notifies the ruv that there are operations in progress so that + they can be added to the pending list for the appropriate client. */ +int ruv_add_csn_inprogress (RUV *ruv, const CSN *csn) +{ + RUVElement* replica; + char csn_str[CSN_STRSIZE]; + int rc = RUV_SUCCESS; + + PR_ASSERT (ruv && csn); + + /* locate ruvElement */ + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, csn_get_replicaid (csn)); + if (replica == NULL) + { + replica = ruvAddReplicaNoCSN (ruv, csn_get_replicaid (csn), NULL/*purl*/); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: failed to add replica" + " that created csn %s\n", csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_MEMORY_ERROR; + goto done; + } + } + + /* check first that this csn is not already covered by this RUV */ + if (ruv_covers_csn_internal(ruv, csn, PR_FALSE)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: " + "the csn %s has already be seen - ignoring\n", + csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_COVERS_CSN; + goto done; + } + + rc = csnplInsert (replica->csnpl, csn); + if (rc == 1) /* we already seen this csn */ + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: " + "the csn %s has already be seen - ignoring\n", + csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_COVERS_CSN; + } + else if(rc != 0) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: failed to insert csn %s" + " into pending list\n", csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_UNKNOWN_ERROR; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_add_csn_inprogress: successfully inserted csn %s" + " into pending list\n", csn_as_string (csn, PR_FALSE, csn_str)); + rc = RUV_SUCCESS; + } + +done: + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int ruv_cancel_csn_inprogress (RUV *ruv, const CSN *csn) +{ + RUVElement* replica; + int rc = RUV_SUCCESS; + + PR_ASSERT (ruv && csn); + + /* locate ruvElement */ + PR_RWLock_Wlock (ruv->lock); + replica = ruvGetReplica (ruv, csn_get_replicaid (csn)); + if (replica == NULL) + { + /* ONREPL - log error */ + rc = RUV_NOTFOUND; + goto done; + } + + rc = csnplRemove (replica->csnpl, csn); + if (rc != 0) + rc = RUV_NOTFOUND; + else + rc = RUV_SUCCESS; + +done: + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int ruv_update_ruv (RUV *ruv, const CSN *csn, const char *replica_purl, PRBool isLocal) +{ + int rc=RUV_SUCCESS; + char csn_str[CSN_STRSIZE]; + CSN *max_csn; + CSN *first_csn = NULL; + RUVElement *replica; + + PR_ASSERT (ruv && csn); + + PR_RWLock_Wlock (ruv->lock); + + replica = ruvGetReplica (ruv, csn_get_replicaid (csn)); + if (replica == NULL) + { + /* we should have a ruv element at this point because it would have + been added by ruv_add_inprogress function */ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: " + "can't locate RUV element for replica %d\n", csn_get_replicaid (csn)); + goto done; + } + + if (csnplCommit(replica->csnpl, csn) != 0) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "ruv_update_ruv: cannot commit csn %s\n", + csn_as_string(csn, PR_FALSE, csn_str)); + rc = RUV_CSNPL_ERROR; + goto done; + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: " + "successfully committed csn %s\n", csn_as_string(csn, PR_FALSE, csn_str)); + } + + if ((max_csn = csnplRollUp(replica->csnpl, &first_csn)) != NULL) + { +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "ruv_update_ruv: rolled up to csn %s\n", + csn_as_string(max_csn, PR_FALSE, csn_str)); /* XXXggood remove debugging */ +#endif + /* replica object sets min csn for local replica */ + if (!isLocal && replica->min_csn == NULL) { + /* bug 559223 - it seems that, under huge stress, a server might pass + * through this code when more than 1 change has already been sent and commited into + * the pending lists... Therefore, as we are trying to set the min_csn ever + * generated by this replica, we need to set the first_csn as the min csn in the + * ruv */ + set_min_csn_nolock(ruv, first_csn, replica_purl); + } + set_max_csn_nolock(ruv, max_csn, replica_purl); + /* It is possible that first_csn points to max_csn. + We need to free it once */ + if (max_csn != first_csn) { + csn_free(&first_csn); + } + csn_free(&max_csn); + } + +done: + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + +/* Helper functions */ + +static int +ruvInit (RUV **ruv, int initCount) +{ + PR_ASSERT (ruv); + + /* allocate new RUV */ + *ruv = (RUV *)slapi_ch_calloc (1, sizeof (RUV)); + if (ruv == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvInit: memory allocation failed\n"); + return RUV_MEMORY_ERROR; + } + + /* allocate elements */ + (*ruv)->elements = dl_new (); + if ((*ruv)->elements == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvInit: memory allocation failed\n"); + return RUV_MEMORY_ERROR; + } + + dl_init ((*ruv)->elements, initCount); + + /* create lock */ + (*ruv)->lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "ruv_lock"); + if ((*ruv)->lock == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvInit: failed to create lock\n"); + return RUV_NSPR_ERROR; + } + + return RUV_SUCCESS; +} + +static void +ruvFreeReplica (void **data) +{ + RUVElement *element = *(RUVElement**)data; + + if (NULL != element) + { + if (NULL != element->csn) + { + csn_free (&element->csn); + } + if (NULL != element->min_csn) + { + csn_free (&element->min_csn); + } + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&element->replica_purl); + + if (element->csnpl) + { + csnplFree (&(element->csnpl)); + } + slapi_ch_free ((void **)&element); + } +} + +static RUVElement* +ruvAddReplica (RUV *ruv, const CSN *csn, const char *replica_purl) +{ + RUVElement *replica; + + PR_ASSERT (NULL != ruv); + PR_ASSERT (NULL != csn); + + replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement)); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvAddReplica: memory allocation failed\n"); + return NULL; + } + + replica->rid = csn_get_replicaid (csn); +/* PR_ASSERT(replica->rid != READ_ONLY_REPLICA_ID); */ + + replica->csn = csn_dup (csn); + replica->last_modified = current_time(); + replica->min_csn = csn_dup (csn); + + replica->replica_purl = slapi_ch_strdup(replica_purl); + replica->csnpl = csnplNew (); + + dl_add (ruv->elements, replica); + + return replica; +} + +static RUVElement* +ruvAddReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl) +{ + RUVElement *replica; + + PR_ASSERT (NULL != ruv); +/* PR_ASSERT(rid != READ_ONLY_REPLICA_ID); */ + + replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement)); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvAddReplicaNoCSN: memory allocation failed\n"); + return NULL; + } + + replica->rid = rid; + replica->replica_purl = slapi_ch_strdup(replica_purl); + replica->csnpl = csnplNew (); + + dl_add (ruv->elements, replica); + + return replica; +} + +static RUVElement* +ruvAddIndexReplicaNoCSN (RUV *ruv, ReplicaId rid, const char *replica_purl, int index) +{ + RUVElement *replica; + + PR_ASSERT (NULL != ruv); +/* PR_ASSERT(rid != READ_ONLY_REPLICA_ID); */ + + replica = (RUVElement *)slapi_ch_calloc (1, sizeof (RUVElement)); + if (replica == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "ruvAddIndexReplicaNoCSN: memory allocation failed\n"); + return NULL; + } + + replica->rid = rid; + replica->replica_purl = slapi_ch_strdup(replica_purl); + replica->csnpl = csnplNew (); + + dl_add_index (ruv->elements, replica, index); + + return replica; +} + +static RUVElement * +ruvGetReplica (const RUV *ruv, ReplicaId rid) +{ + PR_ASSERT (ruv /* && rid >= 0 -- rid can't be negative */); + + return (RUVElement *)dl_get (ruv->elements, (const void*)&rid, ruvReplicaCompare); +} + +static int +ruvReplicaCompare (const void *el1, const void *el2) +{ + RUVElement *replica = (RUVElement*)el1; + ReplicaId *rid1 = (ReplicaId*) el2; + + if (replica == NULL || rid1 == NULL) + return -1; + + if (*rid1 == replica->rid) + return 0; + + if (*rid1 < replica->rid) + return -1; + else + return 1; +} + + + +/* + * Given a berval that points to a string of the form: + * "{dbgen} generation-id", return a copy of the + * "generation-id" part in a null-terminated string. + * Returns NULL if the berval is malformed. + */ +static char * +get_replgen_from_berval(const struct berval *bval) +{ + char *ret_string = NULL; + + if (NULL != bval && NULL != bval->bv_val && + (bval->bv_len > strlen(prefix_replicageneration)) && + strncasecmp(bval->bv_val, prefix_replicageneration, + strlen(prefix_replicageneration)) == 0) + { + unsigned int index = strlen(prefix_replicageneration); + /* Skip any whitespace */ + while (index++ < bval->bv_len && bval->bv_val[index] == ' '); + if (index < bval->bv_len) + { + unsigned int ret_len = bval->bv_len - index; + ret_string = slapi_ch_malloc(ret_len + 1); + memcpy(ret_string, &bval->bv_val[index], ret_len); + ret_string[ret_len] = '\0'; + } + } + return ret_string; +} + + + +/* + * Given a berval that points to a string of the form: + * "{replica ldap[s]//host:port} <min_csn> <csn>", parse out the + * partial URL and the CSNs into an RUVElement, and return + * a pointer to the copy. Returns NULL if the berval is + * malformed. + */ +static RUVElement * +get_ruvelement_from_berval(const struct berval *bval) +{ + RUVElement *ret_ruve = NULL; + char *purl = NULL; + ReplicaId rid = 0; + char ridbuff [RIDSTR_SIZE]; + int i; + + if (NULL != bval && NULL != bval->bv_val && + bval->bv_len > strlen(prefix_ruvcsn) && + strncasecmp(bval->bv_val, prefix_ruvcsn, strlen(prefix_ruvcsn)) == 0) + { + unsigned int urlbegin = strlen(prefix_ruvcsn); + unsigned int urlend; + unsigned int mincsnbegin; + + /* replica id must be here */ + i = 0; + while (isdigit (bval->bv_val[urlbegin])) + { + ridbuff [i] = bval->bv_val[urlbegin]; + i++; + urlbegin ++; + } + + if (i == 0) /* replicaid is missing */ + goto loser; + + ridbuff[i] = '\0'; + rid = atoi (ridbuff); + + if (bval->bv_val[urlbegin] == '}') + { + /* No purl in this value */ + purl = NULL; + mincsnbegin = urlbegin + 1; + } + else + { + while (urlbegin++ < bval->bv_len && bval->bv_val[urlbegin] == ' '); + urlend = urlbegin; + while (urlend++ < bval->bv_len && bval->bv_val[urlend] != '}'); + purl = slapi_ch_malloc(urlend - urlbegin + 1); + memcpy(purl, &bval->bv_val[urlbegin], urlend - urlbegin); + purl[urlend - urlbegin] = '\0'; + mincsnbegin = urlend; + } + /* Skip any whitespace before the first (minimum) CSN */ + while (mincsnbegin++ < (bval->bv_len-1) && bval->bv_val[mincsnbegin] == ' '); + /* Now, mincsnbegin should contain the index of the beginning of the first csn */ + if (mincsnbegin >= bval->bv_len) + { + /* Missing the entire content*/ + if (purl == NULL) + goto loser; + else /* we have just purl - no changes from the replica has been seen */ + { + ret_ruve = (RUVElement *)slapi_ch_calloc(1, sizeof(RUVElement)); + ret_ruve->rid = rid; + ret_ruve->replica_purl = purl; + } + } + else + { + if (bval->bv_len - mincsnbegin != (_CSN_VALIDCSN_STRLEN * 2) + 1) + { + /* Malformed - incorrect length for 2 CSNs + space */ + goto loser; + } + else + { + char mincsnstr[CSN_STRSIZE]; + char maxcsnstr[CSN_STRSIZE]; + + memset(mincsnstr, '\0', CSN_STRSIZE); + memset(maxcsnstr, '\0', CSN_STRSIZE); + memcpy(mincsnstr, &bval->bv_val[mincsnbegin], _CSN_VALIDCSN_STRLEN); + memcpy(maxcsnstr, &bval->bv_val[mincsnbegin + _CSN_VALIDCSN_STRLEN + 1], _CSN_VALIDCSN_STRLEN); + ret_ruve = (RUVElement *)slapi_ch_calloc(1, sizeof(RUVElement)); + ret_ruve->min_csn = csn_new_by_string(mincsnstr); + ret_ruve->csn = csn_new_by_string(maxcsnstr); + ret_ruve->rid = rid; + ret_ruve->replica_purl = purl; + if (NULL == ret_ruve->min_csn || NULL == ret_ruve->csn) + { + goto loser; + } + } + } + } + + /* initialize csn pending list */ + ret_ruve->csnpl = csnplNew (); + if (ret_ruve->csnpl == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "get_ruvelement_from_berval: failed to create csn pending list\n"); + goto loser; + } + + return ret_ruve; + +loser: + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&purl); + if (NULL != ret_ruve) + { + if (NULL != ret_ruve->min_csn) + { + csn_free(&ret_ruve->min_csn); + } + if (NULL != ret_ruve->csn) + { + csn_free(&ret_ruve->csn); + } + slapi_ch_free((void **)&ret_ruve); + } + return NULL; +} + +int +ruv_move_local_supplier_to_first(RUV *ruv, ReplicaId aRid) +{ + RUVElement * elem = NULL; + int rc = RUV_NOTFOUND; + + PR_ASSERT(ruv); + + PR_RWLock_Wlock (ruv->lock); + + elem = (RUVElement *)dl_delete(ruv->elements,(const void*)&aRid, ruvReplicaCompare, 0); + if (elem) { + dl_add_index(ruv->elements, elem, 1); + rc = RUV_SUCCESS; + } + + PR_RWLock_Unlock (ruv->lock); + + return rc; +} + + +int +ruv_get_first_id_and_purl(RUV *ruv, ReplicaId *rid, char **replica_purl ) +{ + RUVElement * first = NULL; + int cookie; + int rc; + + PR_ASSERT(ruv); + + PR_RWLock_Rlock (ruv->lock); + first = dl_get_first(ruv->elements, &cookie); + if ( first == NULL ) + { + rc = RUV_MEMORY_ERROR; + } + else + { + *rid = first->rid; + *replica_purl = first->replica_purl; + rc = RUV_SUCCESS; + } + PR_RWLock_Unlock (ruv->lock); + return rc; +} + +int ruv_local_contains_supplier(RUV *ruv, ReplicaId rid) +{ + int cookie; + RUVElement *elem = NULL; + + PR_ASSERT(ruv); + + PR_RWLock_Rlock (ruv->lock); + for (elem = dl_get_first (ruv->elements, &cookie); + elem; + elem = dl_get_next (ruv->elements, &cookie)) + { + if (elem->rid == rid){ + PR_RWLock_Unlock (ruv->lock); + return 1; + } + } + PR_RWLock_Unlock (ruv->lock); + return 0; +} + +PRBool ruv_has_csns(const RUV *ruv) +{ + PRBool retval = PR_TRUE; + CSN *mincsn = NULL; + CSN *maxcsn = NULL; + + ruv_get_min_csn(ruv, &mincsn); + ruv_get_max_csn(ruv, &maxcsn); + if (mincsn) { + csn_free(&mincsn); + csn_free(&maxcsn); + } else if (maxcsn) { + csn_free(&maxcsn); + } else { + retval = PR_FALSE; /* both min and max are false */ + } + + return retval; +} + +/* Check if the first ruv is newer than the second one */ +PRBool +ruv_is_newer (Object *sruvobj, Object *cruvobj) +{ + RUV *sruv, *cruv; + RUVElement *sreplica, *creplica; + int scookie, ccookie; + int is_newer = PR_FALSE; + + if ( sruvobj == NULL ) { + return 0; + } + if ( cruvobj == NULL ) { + return 1; + } + sruv = (RUV *) object_get_data ( sruvobj ); + cruv = (RUV *) object_get_data ( cruvobj ); + + for (sreplica = dl_get_first (sruv->elements, &scookie); sreplica; + sreplica = dl_get_next (sruv->elements, &scookie)) + { + /* A hub may have a dummy ruv with rid 65535 */ + if ( sreplica->csn == NULL ) continue; + + for (creplica = dl_get_first (cruv->elements, &ccookie); creplica; + creplica = dl_get_next (cruv->elements, &ccookie)) + { + if ( sreplica->rid == creplica->rid ) { + if ( csn_compare ( sreplica->csn, creplica->csn ) > 0 ) { + is_newer = PR_TRUE; + } + break; + } + } + if ( creplica == NULL || is_newer ) { + is_newer = PR_TRUE; + break; + } + } + + return is_newer; +} + +#ifdef TESTING /* Some unit tests for code in this file */ + +static void +ruv_dump_internal(RUV *ruv) +{ + RUVElement *replica; + int cookie; + char csnstr1[CSN_STRSIZE]; + char csnstr2[CSN_STRSIZE]; + + PR_ASSERT(NULL != ruv); + printf("{replicageneration} %s\n", ruv->replGen == NULL ? "NULL" : ruv->replGen); + for (replica = dl_get_first (ruv->elements, &cookie); replica; + replica = dl_get_next (ruv->elements, &cookie)) + { + printf("{replica%s%s} %s %s\n", + replica->replica_purl == NULL ? "" : " ", + replica->replica_purl == NULL ? "" : replica->replica_purl, + csn_as_string(replica->min_csn, PR_FALSE, csnstr1), + csn_as_string(replica->csn, PR_FALSE, csnstr2)); + } +} + +void +ruv_test() +{ + const struct berval *vals[5]; + struct berval val0, val1, val2, val3; + RUV *ruv; + Slapi_Attr *attr; + Slapi_Value *sv0, *sv1, *sv2, *sv3; + int rc; + char csnstr[CSN_STRSIZE]; + char *gen; + CSN *newcsn; + ReplicaId *ids; + int nids; + Slapi_Mod smods; + PRBool covers; + + vals[0] = &val0; + vals[1] = &val1; + vals[2] = &val2; + vals[3] = &val3; + vals[4] = NULL; + + val0.bv_val = "{replicageneration} 0440FDC0A33F"; + val0.bv_len = strlen(val0.bv_val); + + val1.bv_val = "{replica ldap://ggood.mcom.com:389} 12345670000000FE0000 12345671000000FE0000"; + val1.bv_len = strlen(val1.bv_val); + + val2.bv_val = "{replica ldaps://an-impossibly-long-host-name-that-drags-on-forever-and-forever.mcom.com:389} 11112110000000FF0000 11112111000000FF0000"; + val2.bv_len = strlen(val2.bv_val); + + val3.bv_val = "{replica} 12345672000000FD0000 12345673000000FD0000"; + val3.bv_len = strlen(val3.bv_val); + + rc = ruv_init_from_bervals(vals, &ruv); + ruv_dump_internal(ruv); + + attr = slapi_attr_new(); + attr = slapi_attr_init(attr, "ruvelement"); + sv0 = slapi_value_new(); + sv1 = slapi_value_new(); + sv2 = slapi_value_new(); + sv3 = slapi_value_new(); + slapi_value_init_berval(sv0, &val0); + slapi_value_init_berval(sv1, &val1); + slapi_value_init_berval(sv2, &val2); + slapi_value_init_berval(sv3, &val3); + slapi_attr_add_value(attr, sv0); + slapi_attr_add_value(attr, sv1); + slapi_attr_add_value(attr, sv2); + slapi_attr_add_value(attr, sv3); + rc = ruv_init_from_slapi_attr(attr, &ruv); + ruv_dump_internal(ruv); + + rc = ruv_delete_replica(ruv, 0xFF); + /* Should delete one replica */ + ruv_dump_internal(ruv); + + rc = ruv_delete_replica(ruv, 0xAA); + /* No such replica - should not do anything */ + ruv_dump_internal(ruv); + + rc = ruv_get_largest_csn_for_replica(ruv, 0xFE, &newcsn); + if (NULL != newcsn) + { + csn_as_string(newcsn, PR_FALSE, csnstr); + printf("Replica 0x%X has largest csn \"%s\"\n", 0xFE, csnstr); + } + else + { + printf("BAD - can't get largest CSN for replica 0x%X\n", 0xFE); + } + + rc = ruv_get_smallest_csn_for_replica(ruv, 0xFE, &newcsn); + if (NULL != newcsn) + { + csn_as_string(newcsn, PR_FALSE, csnstr); + printf("Replica 0x%X has smallest csn \"%s\"\n", 0xFE, csnstr); + } + else + { + printf("BAD - can't get smallest CSN for replica 0x%X\n", 0xFE); + } + rc = ruv_get_largest_csn_for_replica(ruv, 0xAA, &newcsn); + printf("ruv_get_largest_csn_for_replica on non-existent replica ID returns %d\n", rc); + + rc = ruv_get_smallest_csn_for_replica(ruv, 0xAA, &newcsn); + printf("ruv_get_smallest_csn_for_replica on non-existent replica ID returns %d\n", rc); + + newcsn = csn_new_by_string("12345674000000FE0000"); /* Old replica 0xFE */ + rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com"); + /* Should update replica FE's CSN */ + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("12345675000000FB0000"); /* New replica 0xFB */ + rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com"); + /* Should get a new replica in the list with min == max csn */ + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("12345676000000FD0000"); /* Old replica 0xFD */ + rc = ruv_set_csns(ruv, newcsn, "ldaps://foobar.mcom.com"); + /* Should update replica 0xFD so new CSN is newer than min CSN */ + ruv_dump_internal(ruv); + + gen = ruv_get_replica_generation(ruv); + printf("replica generation is \"%s\"\n", gen); + + newcsn = csn_new_by_string("12345673000000FE0000"); /* Old replica 0xFE */ + covers = ruv_covers_csn(ruv, newcsn); /* should say "true" */ + + newcsn = csn_new_by_string("12345675000000FE0000"); /* Old replica 0xFE */ + covers = ruv_covers_csn(ruv, newcsn); /* Should say "false" */ + + newcsn = csn_new_by_string("123456700000000A0000"); /* New replica 0A */ + rc = ruv_set_min_csn(ruv, newcsn, "ldap://repl0a.mcom.com"); + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("123456710000000A0000"); /* New replica 0A */ + rc = ruv_set_max_csn(ruv, newcsn, "ldap://repl0a.mcom.com"); + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("123456700000000B0000"); /* New replica 0B */ + rc = ruv_set_max_csn(ruv, newcsn, "ldap://repl0b.mcom.com"); + ruv_dump_internal(ruv); + + newcsn = csn_new_by_string("123456710000000B0000"); /* New replica 0B */ + rc = ruv_set_min_csn(ruv, newcsn, "ldap://repl0b.mcom.com"); + ruv_dump_internal(ruv); + + /* ONREPL test ruv enumeration */ + + rc = ruv_to_smod(ruv, &smods); + + ruv_destroy(&ruv); +} +#endif /* TESTING */ diff --git a/ldap/servers/plugins/replication/repl5_ruv.h b/ldap/servers/plugins/replication/repl5_ruv.h new file mode 100644 index 00000000..8484e74b --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_ruv.h @@ -0,0 +1,88 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_ruv.h - interface for replica update vector */ + +#ifndef REPL5_RUV +#define REPL5_RUV + +#include "slapi-private.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _ruv RUV; + +enum +{ + RUV_SUCCESS=0, + RUV_BAD_DATA, + RUV_NOTFOUND, + RUV_MEMORY_ERROR, + RUV_NSPR_ERROR, + RUV_BAD_FORMAT, + RUV_UNKNOWN_ERROR, + RUV_ALREADY_EXIST, + RUV_CSNPL_ERROR, + RUV_COVERS_CSN +}; + +typedef struct ruv_enum_data +{ + CSN *csn; + CSN *min_csn; +} ruv_enum_data; + +typedef int (*FNEnumRUV) (const ruv_enum_data *element, void *arg); +int ruv_init_new (const char *replGen, ReplicaId rid, const char *purl, RUV **ruv); +int ruv_init_from_bervals(struct berval** vals, RUV **ruv); +int ruv_init_from_slapi_attr(Slapi_Attr *attr, RUV **ruv); +int ruv_init_from_slapi_attr_and_check_purl(Slapi_Attr *attr, RUV **ruv, ReplicaId *rid); +RUV* ruv_dup (const RUV *ruv); +void ruv_destroy (RUV **ruv); +void ruv_copy_and_destroy (RUV **srcruv, RUV **destruv); +int ruv_replace_replica_purl (RUV *ruv, ReplicaId rid, const char *replica_purl); +int ruv_delete_replica (RUV *ruv, ReplicaId rid); +int ruv_add_replica (RUV *ruv, ReplicaId rid, const char *replica_purl); +int ruv_add_index_replica (RUV *ruv, ReplicaId rid, const char *replica_purl, int index); +PRBool ruv_contains_replica (const RUV *ruv, ReplicaId rid); +int ruv_get_largest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn); +int ruv_get_smallest_csn_for_replica(const RUV *ruv, ReplicaId rid, CSN **csn); +int ruv_set_csns(RUV *ruv, const CSN *csn, const char *replica_purl); +int ruv_set_csns_keep_smallest(RUV *ruv, const CSN *csn); +int ruv_set_max_csn(RUV *ruv, const CSN *max_csn, const char *replica_purl); +int ruv_set_min_csn(RUV *ruv, const CSN *min_csn, const char *replica_purl); +const char *ruv_get_purl_for_replica(const RUV *ruv, ReplicaId rid); +char *ruv_get_replica_generation (const RUV *ruv); +void ruv_set_replica_generation (RUV *ruv, const char *generation); +PRBool ruv_covers_ruv(const RUV *covering_ruv, const RUV *covered_ruv); +PRBool ruv_covers_csn(const RUV *ruv, const CSN *csn); +PRBool ruv_covers_csn_strict(const RUV *ruv, const CSN *csn); +int ruv_get_min_csn(const RUV *ruv, CSN **csn); +int ruv_get_max_csn(const RUV *ruv, CSN **csn); +int ruv_enumerate_elements (const RUV *ruv, FNEnumRUV fn, void *arg); +int ruv_to_smod(const RUV *ruv, Slapi_Mod *smod); +int ruv_last_modified_to_smod(const RUV *ruv, Slapi_Mod *smod); +int ruv_to_bervals(const RUV *ruv, struct berval ***bvals); +PRInt32 ruv_replica_count (const RUV *ruv); +char **ruv_get_referrals(const RUV *ruv); +void ruv_dump(const RUV *ruv, char *ruv_name, PRFileDesc *prFile); +int ruv_add_csn_inprogress (RUV *ruv, const CSN *csn); +int ruv_cancel_csn_inprogress (RUV *ruv, const CSN *csn); +int ruv_update_ruv (RUV *ruv, const CSN *csn, const char *replica_purl, PRBool isLocal); +int ruv_move_local_supplier_to_first(RUV *ruv, ReplicaId rid); +int ruv_get_first_id_and_purl(RUV *ruv, ReplicaId *rid, char **replica_purl ); +int ruv_local_contains_supplier(RUV *ruv, ReplicaId rid); +/* returns true if the ruv has any csns, false otherwise - used for testing + whether or not an RUV is empty */ +PRBool ruv_has_csns(const RUV *ruv); +PRBool ruv_is_newer (Object *sruv, Object *cruv); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ldap/servers/plugins/replication/repl5_schedule.c b/ldap/servers/plugins/replication/repl5_schedule.c new file mode 100644 index 00000000..427e79a9 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_schedule.c @@ -0,0 +1,742 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl5_schedule.c */ +/* + +The schedule object implements the scheduling policy for a DS 5.0 replication +supplier. + +Methods: +schedule_set() - sets the schedule +schedule_get() - gets the schedule +schedule_in_window_now() - returns TRUE if a replication session + should commence. +schedule_next() - returns the next time that replication is + scheduled to occur. +schedule_notify() - called to inform the scheduler when entries + have been updated. +schedule_set_priority_attributes() - sets the attributes that are + considered "high priority". A modification to one of these attributes + will cause replication to commence asap, overriding the startup + delay and maximum backlog. Also includes an additional parameter + that controls whether priority attributes are propagated regardless + of the scheduling window, e.g. it's possible to configure things + so that password changes get propagated even if we're not in a + replication window. +schedule_set_startup_delay() - sets the time that replication should + wait before commencing replication sessions. +schedule_set_maximum_backlog() - sets the maximum number of updates + which can occur before replication will commence. If the backlog + threshhold is exceeded, then replication will commence ASAP, + overriding the startup delay. + +*/ + +/* ONREPL - I made a simplifying assumption that a schedule item does not + cross day boundaries. Implementing this is hard because we search + for the items for a particular day only based on the item's staring time. + For instance if the current time is tuesday morning, we would not consider + the item that started on monday and continued through tuesday. + To simulate an item that crosses day boundaries, you can create 2 items - + one for the time in the first day and one for the time in the second. + We could do this internally by allowing items do span 2 days and + splitting them ourselves. This, however, is not currently implemented */ + +#include "slapi-plugin.h" +#include "repl5.h" + +#include <ctype.h> /* For isdigit() */ + +/* from proto-slap.h */ +char *get_timestring(time_t *t); +void free_timestring(char *timestr); + +typedef struct schedule_item { + struct schedule_item *next; + PRUint32 start; /* Start time, given as seconds after midnight */ + PRUint32 end; /* End time */ + unsigned char dow; /* Days of week, LSB = Sunday */ +} schedule_item; + +typedef struct schedule { + const char *session_id; + size_t max_backlog; + size_t startup_delay; + schedule_item *schedule_list; /* Linked list of schedule windows */ + char **prio_attrs; /* Priority attributes - start replication now */ + int prio_attrs_override_schedule; + PRTime last_session_end; + int last_session_status; + PRTime last_successful_session_end; + window_state_change_callback callback_fn; /* function to call when window opens/closes */ + void *callback_arg; /* argument to pass to the window state change callback */ + Slapi_Eq_Context pending_event; /* event scheduled with the event queue */ + PRLock *lock; +} schedule; + +/* Forward declarations */ +static schedule_item *parse_schedule_value(const Slapi_Value *v); +static void schedule_window_state_change_event (Schedule *sch); +static void unschedule_window_state_change_event (Schedule *sch); +static void window_state_changed (time_t when, void *arg); +static int schedule_in_window_now_nolock(Schedule *sch); +static schedule_item* get_current_schedule_item (Schedule *sch); +static time_t PRTime2time_t (PRTime tm); +static PRTime schedule_next_nolock (Schedule *sch, PRBool start); +static void free_schedule_list(schedule_item **schedule_list); + +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE) +#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR) +#define DAYS_PER_WEEK 7 +#define ALL_DAYS 0x7F /* Bit mask */ + + + +/* + * Create a new schedule object and return a pointer to it. + */ +Schedule* +schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id) +{ + Schedule *sch = NULL; + sch = (Schedule *)slapi_ch_calloc(1, sizeof(struct schedule)); + + sch->session_id = session_id ? session_id : ""; + sch->callback_fn = callback_fn; + sch->callback_arg = callback_arg; + + if ((sch->lock = PR_NewLock()) == NULL) + { + slapi_ch_free((void **)&sch); + } + + return sch; +} + + +void +schedule_destroy(Schedule *s) +{ + int i; + + /* unschedule update window event if exists */ + unschedule_window_state_change_event (s); + + if (s->schedule_list) + { + free_schedule_list (&s->schedule_list); + } + + if (NULL != s->prio_attrs) + { + for (i = 0; NULL != s->prio_attrs[i]; i++) + { + slapi_ch_free((void **)&(s->prio_attrs[i])); + } + slapi_ch_free((void **)&(s->prio_attrs)); + } + PR_DestroyLock(s->lock); + s->lock = NULL; + slapi_ch_free((void **)&s); +} + +static void +free_schedule_list(schedule_item **schedule_list) +{ + schedule_item *si = *schedule_list; + schedule_item *tmp_si; + while (NULL != si) + { + tmp_si = si->next; + slapi_ch_free((void **)&si); + si = tmp_si; + } + *schedule_list = NULL; +} + + + +/* + * Sets the schedule. Returns 0 if all of the schedule lines were + * correctly parsed and the new schedule has been put into effect. + * Returns -1 if one or more of the schedule items could not be + * parsed. If -1 is returned, then no changes have been made to the + * current schedule. + */ +int +schedule_set(Schedule *sch, Slapi_Attr *attr) +{ + int return_value; + schedule_item *si = NULL; + schedule_item *new_schedule_list = NULL; + int valid = 1; + + if (NULL != attr) + { + int ind; + Slapi_Value *sval; + ind = slapi_attr_first_value(attr, &sval); + while (ind >= 0) + { + si = parse_schedule_value(sval); + if (NULL == si) + { + valid = 0; + break; + } + /* Put at head of linked list */ + si->next = new_schedule_list; + new_schedule_list = si; + ind = slapi_attr_next_value(attr, ind, &sval); + } + } + + if (!valid) + { + /* deallocate any new schedule items */ + free_schedule_list(&new_schedule_list); + return_value = -1; + } + else + { + PR_Lock(sch->lock); + + /* if there is an update window event scheduled - unschedule it */ + unschedule_window_state_change_event (sch); + + free_schedule_list(&sch->schedule_list); + sch->schedule_list = new_schedule_list; + + /* schedule an event to notify the caller about openning/closing of the update window */ + schedule_window_state_change_event (sch); + + PR_Unlock(sch->lock); + return_value = 0; + } + return return_value; +} + + + +/* + * Returns the schedule. + */ +char ** +schedule_get(Schedule *sch) +{ + char **return_value = NULL; + + return return_value; +} + + + +/* + * Return an integer corresponding to the day of the week for + * "when". + */ +static PRInt32 +day_of_week(PRTime when) +{ + + PRExplodedTime exp; + + PR_ExplodeTime(when, PR_LocalTimeParameters, &exp); + return(exp.tm_wday); +} + + +/* + * Return the number of seconds between "when" and the + * most recent midnight. + */ +static PRUint32 +seconds_since_midnight(PRTime when) +{ + PRExplodedTime exp; + + PR_ExplodeTime(when, PR_LocalTimeParameters, &exp); + return(exp.tm_hour * 3600 + exp.tm_min * 60 + exp.tm_sec); +} + + +/* + * Return 1 if "now" is within the schedule window + * specified by "si", 0 otherwise. + */ +static int +time_in_window(PRTime now, schedule_item *si) +{ + unsigned char dow = 1 << day_of_week(now); + int return_value = 0; + + if (dow & si->dow) + { + PRUint32 nowsec = seconds_since_midnight(now); + + return_value = (nowsec >= si->start) && (nowsec <= si->end); + } + + return return_value; +} + + + +/* + * Returns a non-zero value if the current time is within a + * replication window and if scheduling constraints are all met. + * Otherwise, returns zero. + */ + +int +schedule_in_window_now (Schedule *sch) +{ + int rc; + + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + + rc = schedule_in_window_now_nolock(sch); + + PR_Unlock(sch->lock); + + return rc; +} + +/* Must be called under sch->lock */ +static int +schedule_in_window_now_nolock(Schedule *sch) +{ + int return_value = 0; + + if (NULL == sch->schedule_list) + { + /* Absence of a schedule is the same as 0000-2359 0123456 */ + return_value = 1; + } + else + { + schedule_item *si = sch->schedule_list; + PRTime now; + now = PR_Now(); + while (NULL != si) + { + if (time_in_window(now, si)) + { + /* XXX check backoff timers??? */ + return_value = 1; + break; + } + si = si->next; + } + } + + return return_value; +} + + + +/* + * Calculate the next time (expressed as a PRTime) when this + * schedule item will change state (from open to close or vice versa). + */ +static PRTime +next_change_time(schedule_item *si, PRTime now, PRBool start) +{ + PRUint32 nowsec = seconds_since_midnight(now); + PRUint32 sec_til_change; + PRUint32 change_time; + PRExplodedTime exp; + PRInt32 dow = day_of_week(now); + unsigned char dow_bit = 1 << dow; + unsigned char next_dow; + + if (start) /* we are looking for the next window opening */ + { + change_time = si->start; + } + else /* we are looking for the next window closing */ + { + /* open range is inclusive - so we need to add a minute if we are looking for close time */ + change_time = si->end + SECONDS_PER_MINUTE; + } + + /* we are replicating today and next change is also today */ + if ((dow_bit & si->dow) && (nowsec < change_time)) + { + sec_til_change = change_time - nowsec; + } + else /* not replicating today or the change already occured today */ + { + int i; + + /* find next day when we replicate */ + for (i = 1; i <= DAYS_PER_WEEK; i++) + { + next_dow = 1 << ((dow + i) % DAYS_PER_WEEK); + if (next_dow & si->dow) + break; + } + + sec_til_change = change_time + i * SECONDS_PER_DAY - nowsec; + } + + PR_ExplodeTime(now, PR_LocalTimeParameters, &exp); + exp.tm_sec += sec_til_change; + + + PR_NormalizeTime(&exp, PR_LocalTimeParameters); + return PR_ImplodeTime(&exp); +} + + + +/* + * Returns the next time that replication is scheduled to occur. + * Returns 0 if there is no time in the future that replication + * will begin (e.g. there's no schedule at all). + */ +PRTime +schedule_next(Schedule *sch) +{ + PRTime tm; + + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + + tm = schedule_next_nolock (sch, PR_TRUE); + + PR_Unlock(sch->lock); + + return tm; +} + +/* Must be called under sch->lock */ +static PRTime +schedule_next_nolock (Schedule *sch, PRBool start) +{ + + PRTime closest_time = LL_Zero(); + + if (NULL != sch->schedule_list) + { + schedule_item *si = sch->schedule_list; + PRTime now = PR_Now(); + unsigned char dow = 1 << day_of_week(now); + + while (NULL != si) + { + PRTime tmp_time; + + /* Check if this item's change time is sooner than the others */ + tmp_time = next_change_time(si, now, start); + if (LL_IS_ZERO(closest_time)) + { + LL_ADD(closest_time, tmp_time, LL_Zero()); /* Really just an asignment */ + } + else if (LL_CMP(tmp_time, <, closest_time)) + { + LL_ADD(closest_time, tmp_time, LL_Zero()); /* Really just an asignment */ + } + + si = si->next; + } + } + + return closest_time; +} + + + + +/* + * Called by the enclosing object (replsupplier) when a change within the + * replicated area has occurred. This allows the scheduler to update its + * internal counters, timers, etc. Returns a non-zero value if replication + * should commence, zero if it should not. + */ +int +schedule_notify(Schedule *sch, Slapi_PBlock *pb) +{ + int return_value = 0; + + return return_value; +} + + + + +/* + * Provide a list of attributes which, if changed, + * will cause replication to commence as soon as possible. There + * is also a flag that tells the scheduler if the update of a + * priority attribute should cause the schedule to be overridden, + * e.g. if the administrator wants password changes to propagate + * even if not in a replication window. + * + * This function consumes "prio_attrs" and assumes management + * of the memory. + */ +void +schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + if (NULL != sch->prio_attrs) + { + int i; + for (i = 0; NULL != prio_attrs[i]; i++) { + slapi_ch_free((void **)&sch->prio_attrs[i]); + } + slapi_ch_free((void **)&sch->prio_attrs); + } + sch->prio_attrs = prio_attrs; + sch->prio_attrs_override_schedule = override_schedule; + + PR_Unlock(sch->lock); +} + + + + + +/* + * Set the time, in seconds, that replication will wait after a change is + * available before propagating it. This capability will allow multiple + * updates to be coalesced into a single replication session. + */ +void +schedule_set_startup_delay(Schedule *sch, size_t startup_delay) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + sch->startup_delay = startup_delay; + PR_Unlock(sch->lock); +} + + + + + +/* + * Set the maximum number of pending changes allowed to accumulate + * before a replication session is begun. + */ +void +schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + sch->max_backlog = max_backlog; + PR_Unlock(sch->lock); +} + + + + + +/* + * Notify the scheduler that a replication session completed at a certain + * time. There is also a status argument that says more about the session's + * termination (normal, abnormal), which the scheduler uses in determining + * the backoff strategy. + */ +void +schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int status) +{ + PR_ASSERT(NULL != sch); + PR_Lock(sch->lock); + sch->last_session_end = session_end_time; + sch->last_session_status = status; + if (REPLICATION_SESSION_SUCCESS == status) + { + sch->last_successful_session_end = session_end_time; + } + PR_Unlock(sch->lock); +} + +/* schedule an event that will fire the next time the update window state + changes from open to closed or vice versa */ +static void +schedule_window_state_change_event (Schedule *sch) +{ + time_t wakeup_time; + PRTime tm; + int window_opened; + char *timestr = NULL; + + /* if we have a schedule and a callback function is registerd - + register an event with the event queue */ + if (sch->schedule_list && sch->callback_fn) + { + /* ONREPL what if the window is really small and by the time we are done + with the computation - we cross window boundary. + I think we should put some constrains on schedule to avoid that */ + + window_opened = schedule_in_window_now_nolock(sch); + + tm = schedule_next_nolock(sch, !window_opened); + + wakeup_time = PRTime2time_t (tm); + + /* schedule the event */ + sch->pending_event = slapi_eq_once(window_state_changed, sch, wakeup_time); + + timestr = get_timestring(&wakeup_time); + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "%s: Update window will %s at %s\n", + sch->session_id, + window_opened ? "close" : "open", timestr); + free_timestring(timestr); + timestr = NULL; + } +} + +/* this function is called by the even queue the next time + the window is opened or closed */ +static void +window_state_changed (time_t when, void *arg) +{ + Schedule *sch = (Schedule*)arg; + int open; + + PR_ASSERT (sch); + + PR_Lock(sch->lock); + + open = schedule_in_window_now_nolock(sch); + + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: Update window is now %s\n", + sch->session_id, + open ? "open" : "closed"); + + /* schedule next event */ + schedule_window_state_change_event (sch); + + /* notify the agreement */ + sch->callback_fn (sch->callback_arg, open); + + PR_Unlock(sch->lock); +} + +/* cancel the event registered with the event queue */ +static void +unschedule_window_state_change_event (Schedule *sch) +{ + if (sch->pending_event) + { + slapi_eq_cancel(sch->pending_event); + sch->pending_event = NULL; + } +} + +static time_t +PRTime2time_t (PRTime tm) +{ + PRInt64 rt; + + PR_ASSERT (tm); + + LL_DIV(rt, tm, PR_USEC_PER_SEC); + + return (time_t)rt; +} + +/* + * Parse a schedule line. + * The format is: + * <start>-<end> <day_of_week> + * <start> and <end> are in 24-hour time + * <day_of_week> is like cron(5): 0 = Sunday, 1 = Monday, etc. + * + * The schedule item "*" is equivalen to 0000-2359 0123456 + * + * Returns a pointer to a schedule item on success, NULL if the + * schedule item cannot be parsed. + */ +static schedule_item * +parse_schedule_value(const Slapi_Value *v) +{ +#define RANGE_VALID(p, limit) \ + ((p + 9) < limit && \ + isdigit(p[0]) && \ + isdigit(p[1]) && \ + isdigit(p[2]) && \ + isdigit(p[3]) && \ + ('-' == p[4]) && \ + isdigit(p[5]) && \ + isdigit(p[6]) && \ + isdigit(p[7]) && \ + isdigit(p[8])) + + schedule_item *si = NULL; + int valid = 0; + const struct berval *sch_bval; + + if (NULL != v && (sch_bval = slapi_value_get_berval(v)) != NULL && + NULL != sch_bval && sch_bval->bv_len > 0 && NULL != sch_bval->bv_val ) + { + char *p = sch_bval->bv_val; + char *limit = p + sch_bval->bv_len; + + si = (schedule_item *)slapi_ch_malloc(sizeof(schedule_item)); + si->next = NULL; + si->start = 0UL; + si->end = SECONDS_PER_DAY; + si->dow = ALL_DAYS; + + if (*p == '*') + { + valid = 1; + goto done; + } + else + { + if (RANGE_VALID(p, limit)) + { + si->start = ((strntoul(p, 2, 10) * 60) + + strntoul(p + 2, 2, 10)) * 60; + p += 5; + si->end = ((strntoul(p, 2, 10) * 60) + + strntoul(p + 2, 2, 10)) * 60; + p += 4; + + /* ONREPL - for now wi don't allow items that span multiple days. + See note in the beginning of the file for more details. */ + /* ONREPL - we should also decide on the minimum of the item size */ + if (si->start > si->end) + { + valid = 0; + goto done; + } + + if (p < limit && ' ' == *p) + { + /* Specific days of week */ + si->dow = 0; + while (++p < limit) + { + if (!isdigit(*p)) + { + valid = 0; + goto done; + } + si->dow |= (1 << strntoul(p, 1, 10)); + + } + } + valid = 1; + } + } + } + +done: + if (!valid) + { + slapi_ch_free((void **)&si); + } + return si; +} diff --git a/ldap/servers/plugins/replication/repl5_tot_protocol.c b/ldap/servers/plugins/replication/repl5_tot_protocol.c new file mode 100644 index 00000000..45e91f3b --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_tot_protocol.c @@ -0,0 +1,372 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_tot_protocol.c */ +/* + + The tot_protocol object implements the DS 5.0 multi-master total update + replication protocol, used to (re)populate a replica. + +*/ + +#include "repl.h" +#include "repl5.h" +#include "repl5_prot_private.h" + +/* Private data structures */ +typedef struct repl5_tot_private +{ + Repl_Protocol *rp; + Repl_Agmt *ra; + PRLock *lock; + PRUint32 eventbits; +} repl5_tot_private; + +typedef struct callback_data +{ + Private_Repl_Protocol *prp; + int rc; + unsigned long num_entries; + time_t sleep_on_busy; + time_t last_busy; +} callback_data; + +/* + * Number of window seconds to wait until we programmatically decide + * that the replica has got out of BUSY state + */ +#define SLEEP_ON_BUSY_WINDOW (10) + +/* Helper functions */ +static void get_result (int rc, void *cb_data); +static int send_entry (Slapi_Entry *e, void *callback_data); +static void repl5_tot_delete(Private_Repl_Protocol **prp); + +/* + * Completely refresh a replica. The basic protocol interaction goes + * like this: + * - Acquire Replica by sending a StartReplicationRequest extop, with the + * total update protocol OID and supplier's ruv. + * - Send a series of extended operations containing entries. + * - send an EndReplicationRequest extended operation + */ +static void +repl5_tot_run(Private_Repl_Protocol *prp) +{ + int rc; + callback_data cb_data; + Slapi_PBlock *pb; + LDAPControl **ctrls; + PRBool replica_acquired = PR_FALSE; + char *hostname = NULL; + int portnum = 0; + Slapi_DN *area_sdn = NULL; + CSN *remote_schema_csn = NULL; + + PR_ASSERT(NULL != prp); + + prp->stopped = 0; + if (prp->terminate) + { + prp->stopped = 1; + goto done; + } + + conn_set_timeout(prp->conn, agmt_get_timeout(prp->agmt)); + + /* acquire remote replica */ + agmt_set_last_init_start(prp->agmt, current_time()); + rc = acquire_replica (prp, REPL_NSDS50_TOTAL_PROTOCOL_OID, NULL /* ruv */); + /* We never retry total protocol, even in case a transient error. + This is because if somebody already updated the replica we don't + want to do it again */ + if (rc != ACQUIRE_SUCCESS) + { + int optype, ldaprc; + conn_get_error(prp->conn, &optype, &ldaprc); + agmt_set_last_init_status(prp->agmt, ldaprc, + prp->last_acquire_response_code, NULL); + goto done; + } + else if (prp->terminate) + { + conn_disconnect(prp->conn); + prp->stopped = 1; + goto done; + } + + hostname = agmt_get_hostname(prp->agmt); + portnum = agmt_get_port(prp->agmt); + + agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update in progress"); + remote_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt ); + rc = conn_push_schema(prp->conn, &remote_schema_csn); + if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable to " + "replicate schema to host %s, port %d. Continuing with " + "total update session.\n", + hostname, portnum); + /* But keep going */ + agmt_set_last_init_status(prp->agmt, 0, rc, "Total schema update failed"); + } + else + { + agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update succeeded"); + } + + /* ONREPL - big assumption here is that entries a returned in the id order + and that the order implies that perent entry is always ahead of the + child entry in the list. Otherwise, the consumer would not be + properly updated because bulk import at the moment skips orphand entries. */ + /* XXXggood above assumption may not be valid if orphaned entry moved???? */ + + agmt_set_last_init_status(prp->agmt, 0, 0, "Total update in progress"); + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Beginning total update of replica " + "\"%s\".\n", agmt_get_long_name(prp->agmt)); + pb = slapi_pblock_new (); + + /* RMREPL - need to send schema here */ + + area_sdn = agmt_get_replarea(prp->agmt); + /* we need to provide managedsait control so that referral entries can + be replicated */ + ctrls = (LDAPControl **)slapi_ch_calloc (3, sizeof (LDAPControl *)); + ctrls[0] = create_managedsait_control (); + ctrls[1] = create_backend_control(area_sdn); + + slapi_search_internal_set_pb (pb, slapi_sdn_get_dn (area_sdn), + LDAP_SCOPE_SUBTREE, "(|(objectclass=ldapsubentry)(objectclass=nstombstone)(nsuniqueid=*))", NULL, 0, ctrls, NULL, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0); + cb_data.prp = prp; + cb_data.rc = 0; + cb_data.num_entries = 0UL; + cb_data.sleep_on_busy = 0UL; + cb_data.last_busy = current_time (); + + /* this search get all the entries from the replicated area including tombstones + and referrals */ + slapi_search_internal_callback_pb (pb, &cb_data /* callback data */, + get_result /* result callback */, + send_entry /* entry callback */, + NULL /* referral callback*/); + slapi_pblock_destroy (pb); + agmt_set_last_init_end(prp->agmt, current_time()); + rc = cb_data.rc; + release_replica(prp); + slapi_sdn_free(&area_sdn); + + if (rc != LDAP_SUCCESS) + { + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "%s: repl5_tot_run: " + "failed to obtain data to send to the consumer; LDAP error - %d\n", + agmt_get_long_name(prp->agmt), rc); + agmt_set_last_init_status(prp->agmt, rc, 0, "Total update aborted"); + } else { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Finished total update of replica " + "\"%s\". Sent %d entries.\n", agmt_get_long_name(prp->agmt), cb_data.num_entries); + agmt_set_last_init_status(prp->agmt, 0, 0, "Total update succeeded"); + } + +done: + slapi_ch_free_string(&hostname); + prp->stopped = 1; +} + +static int +repl5_tot_stop(Private_Repl_Protocol *prp) +{ + int return_value; + int seconds = 600; + PRIntervalTime start, maxwait, now; + + prp->terminate = 1; + maxwait = PR_SecondsToInterval(seconds); + start = PR_IntervalNow(); + now = start; + while (!prp->stopped && ((now - start) < maxwait)) + { + DS_Sleep(PR_SecondsToInterval(1)); + now = PR_IntervalNow(); + } + if (!prp->stopped) + { + /* Isn't listening. Disconnect from the replica. */ + slapi_log_error (SLAPI_LOG_REPL, repl_plugin_name, "repl5_tot_run: " + "protocol not stopped after waiting for %d seconds " + "for agreement %s\n", PR_IntervalToSeconds(now-start), + agmt_get_long_name(prp->agmt)); + conn_disconnect(prp->conn); + return_value = -1; + } + else + { + return_value = 0; + } + + return return_value; +} + + + +static int +repl5_tot_status(Private_Repl_Protocol *prp) +{ + int return_value = 0; + return return_value; +} + + + +static void +repl5_tot_noop(Private_Repl_Protocol *prp) +{ + /* noop */ +} + + +Private_Repl_Protocol * +Repl_5_Tot_Protocol_new(Repl_Protocol *rp) +{ + repl5_tot_private *rip = NULL; + Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol)); + prp->delete = repl5_tot_delete; + prp->run = repl5_tot_run; + prp->stop = repl5_tot_stop; + prp->status = repl5_tot_status; + prp->notify_update = repl5_tot_noop; + prp->notify_agmt_changed = repl5_tot_noop; + prp->notify_window_opened = repl5_tot_noop; + prp->notify_window_closed = repl5_tot_noop; + prp->update_now = repl5_tot_noop; + if ((prp->lock = PR_NewLock()) == NULL) + { + goto loser; + } + if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL) + { + goto loser; + } + prp->stopped = 1; + prp->terminate = 0; + prp->eventbits = 0; + prp->conn = prot_get_connection(rp); + prp->agmt = prot_get_agreement(rp); + rip = (void *)slapi_ch_malloc(sizeof(repl5_tot_private)); + rip->rp = rp; + prp->private = (void *)rip; + prp->replica_acquired = PR_FALSE; + return prp; +loser: + repl5_tot_delete(&prp); + return NULL; +} + +static void +repl5_tot_delete(Private_Repl_Protocol **prp) +{ +} + +static +void get_result (int rc, void *cb_data) +{ + PR_ASSERT (cb_data); + ((callback_data*)cb_data)->rc = rc; +} + +static +int send_entry (Slapi_Entry *e, void *cb_data) +{ + int rc; + Private_Repl_Protocol *prp; + BerElement *bere; + struct berval *bv; + unsigned long *num_entriesp; + time_t *sleep_on_busyp; + time_t *last_busyp; + + PR_ASSERT (cb_data); + + prp = ((callback_data*)cb_data)->prp; + num_entriesp = &((callback_data *)cb_data)->num_entries; + sleep_on_busyp = &((callback_data *)cb_data)->sleep_on_busy; + last_busyp = &((callback_data *)cb_data)->last_busy; + PR_ASSERT (prp); + + if (prp->terminate) + { + conn_disconnect(prp->conn); + prp->stopped = 1; + ((callback_data*)cb_data)->rc = -1; + return -1; + } + + /* skip ruv tombstone - need to do this because it might be + more up to date then the data we are sending to the client. + RUV is sent separately via the protocol */ + if (is_ruv_tombstone_entry (e)) + return 0; + + /* ONREPL we would purge copiedFrom and copyingFrom here but I decided against it. + Instead, it will get removed when this replica stops being 4.0 consumer and + then propagated to all its consumer */ + + /* convert the entry to the on the wire format */ + bere = entry2bere(e); + if (bere == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: send_entry: Encoding Error\n", + agmt_get_long_name(prp->agmt)); + ((callback_data*)cb_data)->rc = -1; + return -1; + } + + rc = ber_flatten(bere, &bv); + ber_free (bere, 1); + if (rc != 0) + { + ((callback_data*)cb_data)->rc = -1; + return -1; + } + + do { + /* push the entry to the consumer */ + rc = conn_send_extended_operation(prp->conn, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID, + bv /* payload */, NULL /* retoidp */, + NULL /* retdatap */, NULL /* update_control */, + NULL /* returned_controls */); + + if (rc == CONN_BUSY) { + time_t now = current_time (); + if ((now - *last_busyp) < (*sleep_on_busyp + 10)) { + *sleep_on_busyp +=5; + } + else { + *sleep_on_busyp = 5; + } + *last_busyp = now; + + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Replica \"%s\" is busy. Waiting %ds while" + " it finishes processing its current import queue\n", + agmt_get_long_name(prp->agmt), *sleep_on_busyp); + DS_Sleep(PR_SecondsToInterval(*sleep_on_busyp)); + } + } + while (rc == CONN_BUSY); + + ber_bvfree(bv); + (*num_entriesp)++; + + if (CONN_OPERATION_SUCCESS == rc) { + return 0; + } else { + ((callback_data*)cb_data)->rc = rc; + return -1; + } +} + diff --git a/ldap/servers/plugins/replication/repl5_total.c b/ldap/servers/plugins/replication/repl5_total.c new file mode 100644 index 00000000..66dcc353 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_total.c @@ -0,0 +1,869 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + +/* + repl5_total.c - code that implements a total replica update. + + The requestValue of the NSDS50ReplicationEntry looks like this: + + requestValue ::= SEQUENCE { + uniqueid OCTET STRING, + dn LDAPDN, + annotatedAttributes AnnotatedAttributeList + } + + AnnotatedAttributeList ::= SET OF SEQUENCE { + attributeType AttributeDescription, + attributeDeletionCSN OCTET STRING OPTIONAL, + attributeDeleted BOOLEAN DEFAULT FALSE, + annotatedValues SET OF AnnotatedValue + } + + AnnotatedValue ::= SEQUENCE { + value AttributeValue, + valueDeleted BOOLEAN DEFAULT FALSE, + valueCSNSet SEQUENCE OF ValueCSN, + } + + ValueCSN ::= SEQUENCE { + CSNType ENUMERATED { + valuePresenceCSN (1), + valueDeletionCSN (2), + valueDistinguishedCSN (3) + } + CSN OCTET STRING, + } +*/ + +#include "repl5.h" + +#define CSN_TYPE_VALUE_UPDATED_ON_WIRE 1 +#define CSN_TYPE_VALUE_DELETED_ON_WIRE 2 +#define CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE 3 + +/* #define GORDONS_PATENTED_BER_DEBUG 1 */ +#ifdef GORDONS_PATENTED_BER_DEBUG +#define BER_DEBUG(a) printf(a) +#else +#define BER_DEBUG(a) +#endif + +/* Forward declarations */ +static int my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t); +static int my_ber_printf_value(BerElement *ber, const char *type, + const Slapi_Value *value, PRBool deleted); +static int my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted); +static int my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted); +static int my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted); + +/* + * Get a Slapi_Entry ready to send over the wire as part of + * a total update protocol stream. Convert the entry and all + * of its state information to a BerElement which will be the + * payload of an extended LDAP operation. + * + * Entries consist of: + * - An entry DN + * - A uniqueID + * - A set of present attributes, each of which consists of: + * - A set of present values, each of which consists of: + * - A value + * - A set of CSNs + * - A set of deleted values, each of which consists of: + * - A value + * - A set of CSNs + * - A set of deleted attibutes, each of which consists of: + * - An attribute type + * - A set of CSNs. Note that this list of CSNs will always contain exactly one CSN. + * + * This all gets mashed into one BerElement, ready to be blasted over the wire to + * a replica. + * + */ +BerElement * +entry2bere(const Slapi_Entry *e) +{ + BerElement *ber = NULL; + const char *str = NULL; + const char *dnstr = NULL; + char *type; + Slapi_DN *sdn = NULL; + Slapi_Attr *attr = NULL, *prev_attr; + int rc; + + PR_ASSERT(NULL != e); + + if ((ber = ber_alloc()) == NULL) + { + goto loser; + } + BER_DEBUG("{"); + if (ber_printf(ber, "{") == -1) /* Begin outer sequence */ + { + goto loser; + } + + /* Get the entry's uniqueid */ + if ((str = slapi_entry_get_uniqueid(e)) == NULL) + { + goto loser; + } + BER_DEBUG("s(uniqueid)"); + if (ber_printf(ber, "s", str) == -1) + { + goto loser; + } + + /* Get the entry's DN */ + if ((sdn = slapi_entry_get_sdn((Slapi_Entry *)e)) == NULL) /* XXXggood had to cast away const */ + { + goto loser; + } + if ((dnstr = slapi_sdn_get_dn(sdn)) == NULL) + { + goto loser; + } + BER_DEBUG("s(dn)"); + if (ber_printf(ber, "s", dnstr) == -1) + { + goto loser; + } + + /* Next comes the annoted list of the entry's attributes */ + BER_DEBUG("["); + if (ber_printf(ber, "[") == -1) /* Begin set of attributes */ + { + goto loser; + } + /* + * We iterate over all of the non-deleted attributes first. + */ + slapi_entry_first_attr(e, &attr); + while (NULL != attr) + { + /* ONREPL - skip uniqueid attribute since we already sent uniqueid + This is a hack; need to figure a better way of storing uniqueid + in an entry */ + slapi_attr_get_type (attr, &type); + if (strcasecmp (type, SLAPI_ATTR_UNIQUEID) != 0) + { + /* Process this attribute */ + rc = my_ber_printf_attr (ber, attr, PR_FALSE); + if (rc != 0) + { + goto loser; + } + } + + prev_attr = attr; + slapi_entry_next_attr(e, prev_attr, &attr); + } + + /* + * Now iterate over the deleted attributes. + */ + entry_first_deleted_attribute(e, &attr); + while (attr != NULL) + { + /* Process this attribute */ + rc = my_ber_printf_attr (ber, attr, PR_TRUE); + if (rc != 0) + { + goto loser; + } + entry_next_deleted_attribute(e, &attr); + } + BER_DEBUG("]"); + if (ber_printf(ber, "]") == -1) /* End set for attributes */ + { + goto loser; + } + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End sequence for this entry */ + { + goto loser; + } + + /* If we get here, everything went ok */ + BER_DEBUG("\n"); + goto free_and_return; +loser: + if (NULL != ber) + { + ber_free(ber, 1); + ber = NULL; + } + +free_and_return: + return ber; +} + + +/* + * Helper function - convert a CSN to a string and ber_printf() it. + */ +static int +my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t) +{ + char csn_str[CSN_STRSIZE]; + unsigned long len; + int rc = -1; + int csn_type_as_ber = -1; + + switch (t) + { + case CSN_TYPE_VALUE_UPDATED: + csn_type_as_ber = CSN_TYPE_VALUE_UPDATED_ON_WIRE; + break; + case CSN_TYPE_VALUE_DELETED: + csn_type_as_ber = CSN_TYPE_VALUE_DELETED_ON_WIRE; + break; + case CSN_TYPE_VALUE_DISTINGUISHED: + csn_type_as_ber = CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE; + break; + case CSN_TYPE_ATTRIBUTE_DELETED: + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_printf_csn: unknown " + "csn type %d encountered.\n", (int)t); + return -1; + } + + csn_as_string(csn,PR_FALSE,csn_str); + + /* we don't send type for attr csn since there is only one */ + if (t == CSN_TYPE_ATTRIBUTE_DELETED) + { + rc = ber_printf(ber, "s", csn_str); + BER_DEBUG("s(csn_str)"); + } + else + { + len = CSN_STRSIZE; + rc = ber_printf(ber, "{es}", csn_type_as_ber, csn_str); + BER_DEBUG("{e(csn type)s(csn)}"); + } + + return rc; +} + + +/* + * Send a single annotated attribute value. + */ +static int +my_ber_printf_value(BerElement *ber, const char *type, const Slapi_Value *value, PRBool deleted) +{ + const struct berval *bval = NULL; + int rc = -1; + const CSNSet *csnset; + void *cookie; + CSN *csn; + CSNType t; + + bval = slapi_value_get_berval(value); + BER_DEBUG("{o(value)"); + if (ber_printf(ber, "{o", bval->bv_val, bval->bv_len) == -1) /* Start sequence */ + { + goto done; + } + +/* if (ber_printf(ber, "o", bval->bv_val, bval->bv_len) == -1) + { + goto done; + } */ + + if (deleted) + { + BER_DEBUG("b(deleted flag)"); + if (ber_printf (ber, "b", PR_TRUE) == -1) + { + goto done; + } + } + /* Send value CSN list */ + BER_DEBUG("{"); + if (ber_printf(ber, "{") == -1) /* Start set */ + { + goto done; + } + + /* Iterate over the sequence of CSNs. */ + csnset = value_get_csnset (value); + if (csnset) + { + for (cookie = csnset_get_first_csn (csnset, &csn, &t); NULL != cookie; + cookie = csnset_get_next_csn (csnset, cookie, &csn, &t)) + { + /* Don't send any adcsns, since that was already sent */ + if (t != CSN_TYPE_ATTRIBUTE_DELETED) + { + if (my_ber_printf_csn(ber, csn, t) == -1) + { + goto done; + } + } + } + } + + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End CSN sequence */ + { + goto done; + } + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End sequence */ + { + goto done; + } + + /* Everything's ok */ + rc = 0; + +done: + return rc; + +} + +/* send a single attribute */ +static int +my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted) +{ + Slapi_Value *value; + char *type; + int i; + const CSN *csn; + + /* First, send the type */ + slapi_attr_get_type(attr, &type); + BER_DEBUG("{s(type "); + BER_DEBUG(type); + BER_DEBUG(")"); + if (ber_printf(ber, "{s", type) == -1) /* Begin sequence for this type */ + { + goto loser; + } + + /* Send the attribute deletion CSN if present */ + csn = attr_get_deletion_csn(attr); + if (csn) + { + if (my_ber_printf_csn(ber, csn, CSN_TYPE_ATTRIBUTE_DELETED) == -1) + { + goto loser; + } + } + + /* only send "is deleted" flag for deleted attributes since it defaults to false */ + if (deleted) + { + BER_DEBUG("b(del flag)"); + if (ber_printf (ber, "b", PR_TRUE) == -1) + { + goto loser; + } + } + + /* + * Iterate through all the values. + */ + BER_DEBUG("["); + if (ber_printf(ber, "[") == -1) /* Begin set */ + { + goto loser; + } + + /* + * Process the non-deleted values first. + */ + i = slapi_attr_first_value(attr, &value); + while (i != -1) + { + if (my_ber_printf_value(ber, type, value, PR_FALSE) == -1) + { + goto loser; + } + i= slapi_attr_next_value(attr, i, &value); + } + + /* + * Now iterate over all of the deleted values. + */ + i= attr_first_deleted_value(attr, &value); + while (i != -1) + { + if (my_ber_printf_value(ber, type, value, PR_TRUE) == -1) + { + goto loser; + } + i= attr_next_deleted_value(attr, i, &value); + } + BER_DEBUG("]"); + if (ber_printf(ber, "]") == -1) /* End set */ + { + goto loser; + } + + BER_DEBUG("}"); + if (ber_printf(ber, "}") == -1) /* End sequence for this type */ + { + goto loser; + } + + return 0; +loser: + return -1; +} + +/* + * Get an annotated value from the BerElement. Returns 0 on + * success, -1 on failure. + */ +static int +my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted) +{ + struct berval *attrval = NULL; + unsigned long len; + unsigned long tag; + CSN *csn = NULL; + char csnstring[CSN_STRSIZE + 1]; + CSNType csntype; + char *lasti; + + PR_ASSERT(ber && value && deleted); + + *value = NULL; + + if (NULL == ber && NULL == value) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 1\n"); + goto loser; + } + + /* Each value is a sequence */ + if (ber_scanf(ber, "{O", &attrval) == -1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 2\n"); + goto loser; + } + /* Allocate and fill in the attribute value */ + if ((*value = slapi_value_new_berval(attrval)) == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 3\n"); + goto loser; + } + + /* check if this is a deleted value */ + if (ber_peek_tag(ber, &len) == LBER_BOOLEAN) + { + if (ber_scanf(ber, "b", deleted) == -1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 4\n"); + goto loser; + } + } + + else /* default is present value */ + { + *deleted = PR_FALSE; + } + + /* Read the sequence of CSNs */ + for (tag = ber_first_element(ber, &len, &lasti); + tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET; + tag = ber_next_element(ber, &len, lasti)) + { + long csntype_tmp; + /* Each CSN is in a sequence that includes a csntype and CSN */ + len = CSN_STRSIZE; + if (ber_scanf(ber, "{es}", &csntype_tmp, csnstring, &len) == -1) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 7 - bval is %s\n", attrval->bv_val); + goto loser; + } + switch (csntype_tmp) + { + case CSN_TYPE_VALUE_UPDATED_ON_WIRE: + csntype = CSN_TYPE_VALUE_UPDATED; + break; + case CSN_TYPE_VALUE_DELETED_ON_WIRE: + csntype = CSN_TYPE_VALUE_DELETED; + break; + case CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE: + csntype = CSN_TYPE_VALUE_DISTINGUISHED; + break; + default: + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: preposterous CSN type " + "%d received during total update.\n", csntype_tmp); + goto loser; + } + csn = csn_new_by_string(csnstring); + if (csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 8\n"); + goto loser; + } + value_add_csn(*value, csntype, csn); + csn_free (&csn); + } + + if (ber_scanf(ber, "}") == -1) /* End of annotated attribute value seq */ + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "my_ber_scanf_value BAD 10\n"); + goto loser; + } + + if (attrval) + ber_bvfree(attrval); + return 0; + +loser: + /* Free any stuff we allocated */ + if (csn) + csn_free (&csn); + if (attrval) + ber_bvfree(attrval); + if (value) + { + slapi_value_free (value); + } + + return -1; +} + +static int +my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted) +{ + char *attrtype = NULL; + CSN *attr_deletion_csn = NULL; + PRBool val_deleted; + char *lasti; + unsigned long len; + unsigned long tag; + char *str; + int rc; + Slapi_Value *value; + + PR_ASSERT (ber && attr && deleted); + + /* allocate the attribute */ + *attr = slapi_attr_new (); + if (attr == NULL) + { + goto loser; + } + + if (ber_scanf(ber, "{a", &attrtype) == -1) /* Begin sequence for this attr */ + { + goto loser; + } + + + slapi_attr_init(*attr, attrtype); + slapi_ch_free ((void **)&attrtype); + + /* The attribute deletion CSN is next and is optional? */ + if (ber_peek_tag(ber, &len) == LBER_OCTETSTRING) + { + if (ber_scanf(ber, "a", &str) == -1) + { + goto loser; + } + attr_deletion_csn = csn_new_by_string(str); + slapi_ch_free((void **)&str); + } + + if (attr_deletion_csn) + { + rc = attr_set_deletion_csn(*attr, attr_deletion_csn); + csn_free (&attr_deletion_csn); + if (rc != 0) + { + goto loser; + } + } + + /* The "attribute deleted" flag is next, and is optional */ + if (ber_peek_tag(ber, &len) == LBER_BOOLEAN) + { + if (ber_scanf(ber, "b", deleted) == -1) + { + goto loser; + } + } + else /* default is present */ + { + *deleted = PR_FALSE; + } + + /* loop over the list of attribute values */ + for (tag = ber_first_element(ber, &len, &lasti); + tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET; + tag = ber_next_element(ber, &len, lasti)) + { + + value = NULL; + if (my_ber_scanf_value(ber, &value, &val_deleted) == -1) + { + goto loser; + } + + if (val_deleted) + { + /* Add the value to the attribute */ + if (attr_add_deleted_value(*attr, value) == -1) /* attr has ownership of value */ + { + goto loser; + } + } + else + { + /* Add the value to the attribute */ + if (slapi_attr_add_value(*attr, value) == -1) /* attr has ownership of value */ + { + goto loser; + } + } + if (value) + slapi_value_free(&value); + } + + if (ber_scanf(ber, "}") == -1) /* End sequence for this attribute */ + { + goto loser; + } + + return 0; +loser: + if (*attr) + slapi_attr_free (attr); + if (value) + slapi_value_free (&value); + + return -1; +} + +/* + * Extract the payload from a total update extended operation, + * decode it, and produce a Slapi_Entry structure representing a new + * entry to be added to the local database. + */ +static int +decode_total_update_extop(Slapi_PBlock *pb, Slapi_Entry **ep) +{ + BerElement *tmp_bere = NULL; + Slapi_Entry *e = NULL; + Slapi_Attr *attr = NULL; + char *str = NULL; + CSN *dn_csn = NULL; + struct berval *extop_value = NULL; + char *extop_oid = NULL; + unsigned long len; + char *lasto; + unsigned long tag; + int rc; + PRBool deleted; + + PR_ASSERT(NULL != pb); + PR_ASSERT(NULL != ep); + + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid); + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if (NULL == extop_oid || + strcmp(extop_oid, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID) != 0 || + NULL == extop_value) + { + /* Bogus */ + goto loser; + } + + if ((tmp_bere = ber_init(extop_value)) == NULL) + { + goto loser; + } + + if ((e = slapi_entry_alloc()) == NULL) + { + goto loser; + } + + if (ber_scanf(tmp_bere, "{") == -1) /* Begin outer sequence */ + { + goto loser; + } + + /* The entry's uniqueid is first */ + if (ber_scanf(tmp_bere, "a", &str) == -1) + { + goto loser; + } + slapi_entry_set_uniqueid(e, str); + str = NULL; /* Slapi_Entry now owns the uniqueid */ + + /* The entry's DN is next */ + if (ber_scanf(tmp_bere, "a", &str) == -1) + { + goto loser; + } + slapi_entry_set_dn(e, str); + str = NULL; /* Slapi_Entry now owns the dn */ + + /* Get the attributes */ + for ( tag = ber_first_element( tmp_bere, &len, &lasto ); + tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET; + tag = ber_next_element( tmp_bere, &len, lasto ) ) + { + + if (my_ber_scanf_attr (tmp_bere, &attr, &deleted) != 0) + { + goto loser; + } + + /* Add the attribute to the entry */ + if (deleted) + entry_add_deleted_attribute_wsi(e, attr); /* entry now owns attr */ + else + entry_add_present_attribute_wsi(e, attr); /* entry now owns attr */ + attr = NULL; + } + + if (ber_scanf(tmp_bere, "}") == -1) /* End sequence for this entry */ + { + goto loser; + } + + /* Check for ldapsubentries and tombstone entries to set flags properly */ + slapi_entry_attr_find(e, "objectclass", &attr); + if (attr != NULL) { + struct berval bv; + bv.bv_val = "ldapsubentry"; + bv.bv_len = strlen(bv.bv_val); + if (slapi_attr_value_find(attr, &bv) == 0) { + slapi_entry_set_flag(e, SLAPI_ENTRY_LDAPSUBENTRY); + } + bv.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE; + bv.bv_len = strlen(bv.bv_val); + if (slapi_attr_value_find(attr, &bv) == 0) { + slapi_entry_set_flag(e, SLAPI_ENTRY_FLAG_TOMBSTONE); + } + } + + /* If we get here, the entry is properly constructed. Return it. */ + + rc = 0; + *ep = e; + goto free_and_return; + +loser: + rc = -1; + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&str); + + if (NULL != dn_csn) + { + csn_free(&dn_csn); + } + if (attr != NULL) + { + slapi_attr_free (&attr); + } + + if (NULL != e) + { + slapi_entry_free (e); + } + *ep = NULL; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Error: could not decode extended " + "operation containing entry for total update.\n"); + +free_and_return: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + return rc; +} + +/* + * This plugin entry point is called whenever an NSDS50ReplicationEntry + * extended operation is received. + */ +int +multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb) +{ + int rc; + Slapi_Entry *e = NULL; + Slapi_Connection *conn = NULL; + int connid, opid; + + connid = 0; + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + opid = 0; + slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid); + + /* Decode the extended operation */ + rc = decode_total_update_extop(pb, &e); + + if (0 == rc) + { +#ifdef notdef + /* + * Just spew LDIF so we're sure we got it right. Later we'll firehose + * this into the database import code + */ + int len; + char *str = slapi_entry2str_with_options(e, &len,SLAPI_DUMP_UNIQUEID); + puts(str); + free(str); +#endif + + rc = slapi_import_entry (pb, e); + /* slapi_import_entry return an LDAP error in case of problem + * LDAP_BUSY is used to indicate that the import queue is full + * and that flow control must happen to stop the supplier + * from sending entries + */ + if ((rc != LDAP_SUCCESS) && (rc != LDAP_BUSY)) + { + const char *dn = slapi_entry_get_dn_const(e); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Error %d: could not import entry dn %s " + "for total update operation conn=%d op=%d\n", + rc, dn, connid, opid); + rc = -1; + } + + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Error %d: could not decode the total update extop " + "for total update operation conn=%d op=%d\n", + rc, connid, opid); + } + + if ((rc != 0) && (rc != LDAP_BUSY)) + { + /* just disconnect from the supplier. bulk import is stopped when + connection object is destroyed */ + slapi_pblock_get (pb, SLAPI_CONNECTION, &conn); + if (conn) + { + slapi_disconnect_server(conn); + } + + /* cleanup */ + if (e) + { + slapi_entry_free (e); + } + } + + return rc; +} diff --git a/ldap/servers/plugins/replication/repl5_updatedn_list.c b/ldap/servers/plugins/replication/repl5_updatedn_list.c new file mode 100644 index 00000000..02304d19 --- /dev/null +++ b/ldap/servers/plugins/replication/repl5_updatedn_list.c @@ -0,0 +1,243 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl5_updatedn_list.c */ + +/* + This is the internal representation for the list of update DNs in the replica. + The list is implemented as a hash table - the key is the normalized DN, and the + value is the Slapi_DN representation of the DN +*/ + +#include "repl5.h" +#include "plhash.h" + +/* global data */ + +/* typedef ReplicaUpdateDNList PLHashTable; */ + +struct repl_enum_data +{ + FNEnumDN fn; + void *arg; +}; + +/* Forward declarations */ +static PRIntn replica_destroy_hash_entry (PLHashEntry *he, PRIntn index, void *arg); +static PRIntn updatedn_list_enumerate (PLHashEntry *he, PRIntn index, void *hash_data); + +static int +updatedn_compare_dns(const void *d1, const void *d2) +{ + return (0 == slapi_sdn_compare((const Slapi_DN *)d1, (const Slapi_DN *)d2)); +} + +/* create a new updatedn list - if the entry is given, initialize the list from + the replicabinddn values given in the entry */ +ReplicaUpdateDNList +replica_updatedn_list_new(const Slapi_Entry *entry) +{ + /* allocate table */ + PLHashTable *hash = PL_NewHashTable(4, PL_HashString, PL_CompareStrings, + updatedn_compare_dns, NULL, NULL); + if (hash == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_new_updatedn_list: " + "failed to allocate hash table; NSPR error - %d\n", + PR_GetError ()); + return NULL; + } + + if (entry) { + Slapi_Attr *attr = NULL; + if (!slapi_entry_attr_find(entry, attr_replicaBindDn, &attr)) { + Slapi_ValueSet *vs = NULL; + slapi_attr_get_valueset(attr, &vs); + replica_updatedn_list_replace(hash, vs); + slapi_valueset_free(vs); + } + } + + return (ReplicaUpdateDNList)hash; +} + +void +replica_updatedn_list_free(ReplicaUpdateDNList list) +{ + /* destroy the content */ + PLHashTable *hash = list; + PL_HashTableEnumerateEntries(hash, replica_destroy_hash_entry, NULL); + + if (hash) + PL_HashTableDestroy(hash); +} + +void +replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs) +{ + replica_updatedn_list_delete(list, NULL); /* delete all values */ + replica_updatedn_list_add(list, vs); +} + +/* if vs is given, delete only those values - otherwise, delete all values */ +void +replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs) +{ + PLHashTable *hash = list; + if (!vs || slapi_valueset_count(vs) == 0) { /* just delete everything */ + PL_HashTableEnumerateEntries(hash, replica_destroy_hash_entry, NULL); + } else { + Slapi_ValueSet *vs_nc = (Slapi_ValueSet *)vs; /* cast away const */ + Slapi_Value *val = NULL; + int index = 0; + for (index = slapi_valueset_first_value(vs_nc, &val); val; + index = slapi_valueset_next_value(vs_nc, index, &val)) { + Slapi_DN *dn = slapi_sdn_new_dn_byval(slapi_value_get_string(val)); + /* locate object */ + Slapi_DN *deldn = (Slapi_DN *)PL_HashTableLookup(hash, slapi_sdn_get_ndn(dn)); + if (deldn == NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_updatedn_list_delete: " + "update DN with value (%s) is not in the update DN list.\n", + slapi_sdn_get_ndn(dn)); + } else { + /* remove from hash */ + PL_HashTableRemove(hash, slapi_sdn_get_ndn(dn)); + /* free the pointer */ + slapi_sdn_free(&deldn); + } + /* free the temp dn */ + slapi_sdn_free(&dn); + } + } + + return; +} + +void +replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs) +{ + PLHashTable *hash = list; + Slapi_ValueSet *vs_nc = (Slapi_ValueSet *)vs; /* cast away const */ + Slapi_Value *val = NULL; + int index = 0; + + PR_ASSERT(list && vs); + + for (index = slapi_valueset_first_value(vs_nc, &val); val; + index = slapi_valueset_next_value(vs_nc, index, &val)) { + Slapi_DN *dn = slapi_sdn_new_dn_byval(slapi_value_get_string(val)); + const char *ndn = slapi_sdn_get_ndn(dn); + + /* make sure that the name is unique */ + if (PL_HashTableLookup(hash, ndn) != NULL) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replica_updatedn_list_add: " + "update DN with value (%s) already in the update DN list\n", + ndn); + slapi_sdn_free(&dn); + } else { + PL_HashTableAdd(hash, ndn, dn); + } + } + + return; +} + +PRBool +replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn) +{ + PLHashTable *hash = list; + PRBool ret = PR_FALSE; + + const char *ndn = slapi_sdn_get_ndn(dn); + + /* Bug 605169 - null ndn would cause core dump */ + if ( ndn ) { + ret = (PRBool)PL_HashTableLookupConst(hash, ndn); + } + + return ret; +} + +struct list_to_string_data { + char *string; + const char *delimiter; +}; + +static int +convert_to_string(Slapi_DN *dn, void *arg) +{ + struct list_to_string_data *data = (struct list_to_string_data *)arg; + int newlen = strlen(slapi_sdn_get_dn(dn)) + strlen(data->delimiter) + 1; + if (data->string) { + newlen += strlen(data->string); + data->string = slapi_ch_realloc(data->string, newlen); + } else { + data->string = slapi_ch_calloc(1, newlen); + } + strcat(data->string, slapi_sdn_get_dn(dn)); + strcat(data->string, data->delimiter); + + return 1; +} + +/* caller must slapi_ch_free_string the returned string */ +char * +replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter) +{ + struct list_to_string_data data; + data.string = NULL; + data.delimiter = delimiter; + replica_updatedn_list_enumerate(list, convert_to_string, (void *)&data); + return data.string; +} + +void +replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg) +{ + PLHashTable *hash = list; + struct repl_enum_data data; + + PR_ASSERT (fn); + + data.fn = fn; + data.arg = arg; + + PL_HashTableEnumerateEntries(hash, updatedn_list_enumerate, &data); +} + +/* Helper functions */ + +/* this function called for each hash node during hash destruction */ +static PRIntn +replica_destroy_hash_entry(PLHashEntry *he, PRIntn index, void *arg) +{ + Slapi_DN *dn = NULL; + + if (he == NULL) + return HT_ENUMERATE_NEXT; + + dn = (Slapi_DN *)he->value; + PR_ASSERT (dn); + + slapi_sdn_free(&dn); + + return HT_ENUMERATE_REMOVE; +} + +static PRIntn +updatedn_list_enumerate(PLHashEntry *he, PRIntn index, void *hash_data) +{ + Slapi_DN *dn = NULL; + struct repl_enum_data *data = hash_data; + + dn = (Slapi_DN*)he->value; + PR_ASSERT (dn); + + data->fn(dn, data->arg); + + return HT_ENUMERATE_NEXT; +} diff --git a/ldap/servers/plugins/replication/repl_add.c b/ldap/servers/plugins/replication/repl_add.c new file mode 100644 index 00000000..3d47c1db --- /dev/null +++ b/ldap/servers/plugins/replication/repl_add.c @@ -0,0 +1,30 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + + +/* Add Operation Plugin Functions for legacy replication plugin */ + +int +legacy_preop_add( Slapi_PBlock *pb ) +{ + return legacy_preop( pb, "legacy_preop_add", OP_ADD ); +} + +int +legacy_bepreop_add( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + return rc; +} + +int +legacy_postop_add( Slapi_PBlock *pb ) +{ + return legacy_postop( pb, "legacy_postop_add", OP_ADD ); +} diff --git a/ldap/servers/plugins/replication/repl_bind.c b/ldap/servers/plugins/replication/repl_bind.c new file mode 100644 index 00000000..e317e311 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_bind.c @@ -0,0 +1,48 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" + + +int +legacy_preop_bind( Slapi_PBlock *pb ) +{ + int return_value = 0; + char *dn = NULL; + struct berval *cred = NULL; + int method; + int one = 1; + + slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); + slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn); + slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &cred); + + if (LDAP_AUTH_SIMPLE == method) + { + if (legacy_consumer_is_replicationdn(dn) && legacy_consumer_is_replicationpw(cred)) + { + /* Successful bind as replicationdn */ + void *conn = NULL; + consumer_connection_extension *connext = NULL; +#ifdef DEBUG + slapi_log_error(SLAPI_LOG_REPL, REPLICATION_SUBSYSTEM, "legacy_preop_bind: begin\n"); +#endif + slapi_pblock_get( pb, SLAPI_CONNECTION, &conn ); + connext = (consumer_connection_extension*) repl_con_get_ext (REPL_CON_EXT_CONN, conn); + if (NULL != connext) + { + connext->is_legacy_replication_dn = 1; + } + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + return_value = 1; /* Prevent further processing in front end */ + } + } + return return_value; + +} diff --git a/ldap/servers/plugins/replication/repl_compare.c b/ldap/servers/plugins/replication/repl_compare.c new file mode 100644 index 00000000..34f2b944 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_compare.c @@ -0,0 +1,34 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +int +legacy_preop_compare( Slapi_PBlock *pb ) +{ + int is_replicated_operation = 0; + char *compare_base = NULL; + struct berval **referral = NULL; + int return_code = 0; + Slapi_DN *basesdn; + + slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation); + slapi_pblock_get(pb, SLAPI_COMPARE_TARGET, &compare_base); + basesdn= slapi_sdn_new_dn_byref(compare_base); + referral = get_data_source(pb, basesdn, 1, NULL); + slapi_sdn_free(&basesdn); + if (NULL != referral && !is_replicated_operation) + { + /* + * There is a copyingFrom in this entry or an ancestor. + * Return a referral to the supplier, and we're all done. + */ + slapi_send_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, referral); + return_code = 1; /* return 1 to prevent further search processing */ + } + return return_code; +} diff --git a/ldap/servers/plugins/replication/repl_connext.c b/ldap/servers/plugins/replication/repl_connext.c new file mode 100644 index 00000000..8b0c0551 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_connext.c @@ -0,0 +1,91 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl_connext.c - replication extension to the Connection object + */ + + +#include "repl.h" +#include "repl5.h" + + +/* ***** Supplier side ***** */ + +/* NOT NEEDED YET */ + +/* ***** Consumer side ***** */ + +/* consumer connection extension constructor */ +void* consumer_connection_extension_constructor (void *object, void *parent) +{ + consumer_connection_extension *ext = (consumer_connection_extension*) slapi_ch_malloc (sizeof (consumer_connection_extension)); + if (ext == NULL) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication consumer connection extension - out of memory\n" ); + } + else + { + ext->is_legacy_replication_dn= 0; + ext->repl_protocol_version = REPL_PROTOCOL_UNKNOWN; + ext->replica_acquired = NULL; + ext->isreplicationsession= 0; + ext->supplier_ruv = NULL; + ext->connection = NULL; + } + + return ext; +} + +/* consumer connection extension destructor */ +void consumer_connection_extension_destructor (void *ext, void *object, void *parent) +{ + int connid = 0; + if (ext) + { + /* Check to see if this replication session has acquired + * a replica. If so, release it here. + */ + consumer_connection_extension *connext = (consumer_connection_extension *)ext; + if (NULL != connext->replica_acquired) + { + Replica *r = object_get_data ((Object*)connext->replica_acquired); + /* If a total update was in progress, abort it */ + if (REPL_PROTOCOL_50_TOTALUPDATE == connext->repl_protocol_version) + { + Slapi_PBlock *pb = slapi_pblock_new(); + const Slapi_DN *repl_root_sdn = replica_get_root(r); + PR_ASSERT(NULL != repl_root_sdn); + if (NULL != repl_root_sdn) + { + slapi_pblock_set(pb, SLAPI_CONNECTION, connext->connection); + slapi_pblock_set(pb, SLAPI_TARGET_DN, (void*)slapi_sdn_get_dn(repl_root_sdn)); + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "Aborting total update in progress for replicated " + "area %s connid=%d\n", slapi_sdn_get_dn(repl_root_sdn), + connid); + slapi_stop_bulk_import(pb); + } + else + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "consumer_connection_extension_destructor: can't determine root " + "of replicated area.\n"); + } + slapi_pblock_destroy(pb); + } + replica_relinquish_exclusive_access(r, connid, -1); + object_release ((Object*)connext->replica_acquired); + connext->replica_acquired = NULL; + } + + if (connext->supplier_ruv) + { + ruv_destroy ((RUV **)&connext->supplier_ruv); + } + connext->connection = NULL; + slapi_ch_free((void **)&ext); + } +} diff --git a/ldap/servers/plugins/replication/repl_controls.c b/ldap/servers/plugins/replication/repl_controls.c new file mode 100644 index 00000000..ae1cb119 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_controls.c @@ -0,0 +1,337 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl5.h" +#include "repl.h" /* For LDAP_CONTROL_REPL_MODRDN_EXTRAMODS */ + +/* + * repl_controls.c - convenience functions for creating and + * decoding controls that implement 5.0-style replication + * protocol operations. + * + * TODO: Send modrdn mods with modrdn operation + * Fix ber_printf() and ber_scanf() format strings - some are + * the wrong types. + */ + +/* + * Return a pointer to a NSDS50ReplUpdateInfoControl. + * The control looks like this: + * + * NSDS50ReplUpdateInfoControl ::= SEQUENCE { + * uuid OCTET STRING, + * csn OCTET STRING, + * OPTIONAL [new]superior-uuid OCTET STRING + * OPTIONAL modrdn_mods XXXggood WHAT TYPE??? + * } + */ +int +create_NSDS50ReplUpdateInfoControl(const char *uuid, + const char *superior_uuid, const CSN *csn, + LDAPMod **modrdn_mods, LDAPControl **ctrlp) +{ + int retval; + BerElement *tmp_bere = NULL; + struct berval tmpval = {0}; + char csn_str[CSN_STRSIZE]; + + if (NULL == ctrlp) + { + retval = LDAP_PARAM_ERROR; + goto loser; + } + else + { + if ((tmp_bere = ber_alloc()) == NULL) + { + retval = LDAP_NO_MEMORY; + goto loser; + } + else + { + /* Stuff uuid and csn into BerElement */ + if (ber_printf(tmp_bere, "{") == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + /* Stuff uuid of this entry into BerElement */ + if (ber_printf(tmp_bere, "s", uuid) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + /* Stuff csn of this change into BerElement */ + csn_as_string(csn, PR_FALSE, csn_str); + if (ber_printf(tmp_bere, "s", csn_str) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + /* If present, stuff uuid of parent entry into BerElement */ + if (NULL != superior_uuid) + { + if (ber_printf(tmp_bere, "s", superior_uuid) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + } + + /* If present, add the modrdn mods */ + if (NULL != modrdn_mods) + { + int i; + if (ber_printf(tmp_bere, "{" ) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + /* for each modification to be performed... */ + for (i = 0; NULL != modrdn_mods[i]; i++) + { + if (ber_printf(tmp_bere, "{e{s[V]}}", + modrdn_mods[i]->mod_op & ~LDAP_MOD_BVALUES, + modrdn_mods[i]->mod_type, modrdn_mods[i]->mod_bvalues ) == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + } + if (ber_printf(tmp_bere, "}") == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + } + + /* Close the sequence */ + if (ber_printf(tmp_bere, "}") == -1) + { + retval = LDAP_ENCODING_ERROR; + goto loser; + } + + retval = slapi_build_control( REPL_NSDS50_UPDATE_INFO_CONTROL_OID, + tmp_bere, 1 /* is critical */, ctrlp); + } + } +loser: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + return retval; +} + + +/* + * Destroy a ReplUpdateInfoControl and set the pointer to NULL. + */ +void +destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp) +{ + if (NULL != ctrlp && NULL != *ctrlp) + { + ldap_control_free(*ctrlp); + *ctrlp = NULL; + } +} + + + + +/* + * Look through the array of controls. If an NSDS50ReplUpdateInfoControl + * is present, decode it and return pointers to the broken-out + * components. The caller is responsible for freeing pointers to + * the returned objects. The caller may indicate that it is not + * interested in any of the output parameters by passing NULL + * for that parameter. + * + * Returns 0 if the control is not present, 1 if it is present, and + * -1 if an error occurs. + */ +int +decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp, + char **uuid, char **superior_uuid, + CSN **csn, LDAPMod ***modrdn_mods) +{ + struct berval *ctl_value = NULL; + int iscritical = 0; + int rc = -1; + struct berval uuid_val = {0}; + struct berval superior_uuid_val = {0}; + struct berval csn_val = {0}; + BerElement *tmp_bere = NULL; + Slapi_Mods modrdn_smods; + PRBool got_modrdn_mods = PR_FALSE; + unsigned long len; + + slapi_mods_init(&modrdn_smods, 4); + if (slapi_control_present(controlsp, REPL_NSDS50_UPDATE_INFO_CONTROL_OID, + &ctl_value, &iscritical)) + { + if ((tmp_bere = ber_init(ctl_value)) == NULL) + { + rc = -1; + goto loser; + } + if (ber_scanf(tmp_bere, "{oo", &uuid_val, &csn_val) == -1) + { + rc = -1; + goto loser; + } + if (ber_peek_tag(tmp_bere, &len) == LBER_OCTETSTRING) + { + /* The optional superior_uuid is present */ + if (ber_scanf(tmp_bere, "o", &superior_uuid_val) == -1) + { + rc = -1; + goto loser; + } + } + if (ber_peek_tag(tmp_bere, &len) == LBER_SEQUENCE) + { + unsigned long emtag, emlen; + char *emlast; + + for ( emtag = ber_first_element( tmp_bere, &emlen, &emlast ); + emtag != LBER_ERROR && emtag != LBER_END_OF_SEQORSET; + emtag = ber_next_element( tmp_bere, &emlen, emlast )) + { + struct berval **embvals; + long op; + char *type; + if ( ber_scanf( tmp_bere, "{i{a[V]}}", &op, &type, &embvals ) == LBER_ERROR ) + { + rc = -1; + goto loser; + } + slapi_mods_add_modbvps(&modrdn_smods, op, type, embvals); + free( type ); + ber_bvecfree( embvals ); + } + got_modrdn_mods = PR_TRUE; + } + if (ber_scanf(tmp_bere, "}") == -1) + { + rc = -1; + goto loser; + } + + if (NULL != uuid) + { + *uuid = slapi_ch_malloc(uuid_val.bv_len + 1); + strncpy(*uuid, uuid_val.bv_val, uuid_val.bv_len); + (*uuid)[uuid_val.bv_len] = '\0'; + } + + if (NULL != csn) + { + char *csnstr = slapi_ch_malloc(csn_val.bv_len + 1); + strncpy(csnstr, csn_val.bv_val, csn_val.bv_len); + csnstr[csn_val.bv_len] = '\0'; + *csn = csn_new_by_string(csnstr); + slapi_ch_free((void **)&csnstr); + } + + if (NULL != superior_uuid && NULL != superior_uuid_val.bv_val) + { + *superior_uuid = slapi_ch_malloc(superior_uuid_val.bv_len + 1); + strncpy(*superior_uuid, superior_uuid_val.bv_val, + superior_uuid_val.bv_len); + (*superior_uuid)[superior_uuid_val.bv_len] = '\0'; + } + + if (NULL != modrdn_mods && got_modrdn_mods) + { + *modrdn_mods = slapi_mods_get_ldapmods_passout(&modrdn_smods); + } + slapi_mods_done(&modrdn_smods); + + rc = 1; + } + else + { + rc = 0; + } +loser: + /* XXXggood free CSN here if allocated */ + + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + if (NULL != uuid_val.bv_val) + { + ldap_memfree(uuid_val.bv_val); + uuid_val.bv_val = NULL; + } + if (NULL != superior_uuid_val.bv_val) + { + ldap_memfree(superior_uuid_val.bv_val); + superior_uuid_val.bv_val = NULL; + } + if (NULL != csn_val.bv_val) + { + ldap_memfree(csn_val.bv_val); + csn_val.bv_val = NULL; + } + return rc; +} + + + +void +add_repl_control_mods( Slapi_PBlock *pb, Slapi_Mods *smods ) +{ + struct berval *embvp; + LDAPControl **controls = NULL; + + slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls); + if ( slapi_control_present( controls, + LDAP_CONTROL_REPL_MODRDN_EXTRAMODS, + &embvp, NULL )) + { + if ( embvp != NULL && embvp->bv_len > 0 && embvp->bv_val != NULL ) + { + /* Parse the extramods stuff */ + long op; + char *type; + unsigned long emlen; + unsigned long emtag; + char *emlast; + BerElement *ember = ber_init( embvp ); + if ( ember != NULL ) + { + for ( emtag = ber_first_element( ember, &emlen, &emlast ); + emtag != LBER_ERROR && emtag != LBER_END_OF_SEQORSET; + emtag = ber_next_element( ember, &emlen, emlast )) + { + struct berval **embvals; + if ( ber_scanf( ember, "{i{a[V]}}", &op, &type, &embvals ) == LBER_ERROR ) + { + continue; + /* GGOODREPL I suspect this will cause two sets of lastmods attr values + to end up in the entry. We need to remove the old ones. + */ + } + slapi_mods_add_modbvps( smods, op, type, embvals); + free( type ); + ber_bvecfree( embvals ); + } + } + ber_free( ember, 1 ); + } + } +} diff --git a/ldap/servers/plugins/replication/repl_delete.c b/ldap/servers/plugins/replication/repl_delete.c new file mode 100644 index 00000000..6f8e07df --- /dev/null +++ b/ldap/servers/plugins/replication/repl_delete.c @@ -0,0 +1,26 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +int +legacy_preop_delete( Slapi_PBlock *pb ) +{ + return legacy_preop(pb, "legacy_preop_delete", OP_DELETE); +} + +int +legacy_bepreop_delete( Slapi_PBlock *pb ) +{ + return 0; /* OK */ +} + +int +legacy_postop_delete( Slapi_PBlock *pb ) +{ + return legacy_postop(pb, "legacy_preop_delete", OP_DELETE); +} diff --git a/ldap/servers/plugins/replication/repl_entry.c b/ldap/servers/plugins/replication/repl_entry.c new file mode 100644 index 00000000..83bf2857 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_entry.c @@ -0,0 +1,38 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +static int dumping_to_ldif= 0; +static int doing_replica_init= 0; +static char **include_suffix= NULL; + +/* + * This is passed the slapd command line arguments. + */ +void +repl_entry_init(int argc, char** argv) +{ + int i; + for(i=1;i<argc;i++) + { + if(strcmp(argv[i],"db2ldif")==0) + { + dumping_to_ldif= 1; + } + if(strcmp(argv[i],"-r")==0) + { + doing_replica_init= 1; + } + if(strcmp(argv[i],"-s")==0) + { + char *s= slapi_dn_normalize ( slapi_ch_strdup(argv[i+1]) ); + charray_add(&include_suffix,s); + i++; + } + } +} diff --git a/ldap/servers/plugins/replication/repl_ext.c b/ldap/servers/plugins/replication/repl_ext.c new file mode 100644 index 00000000..4ad28726 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_ext.c @@ -0,0 +1,113 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl_ext.c - manages operation extensions created by the + * replication system + */ + + +#include "repl.h" + +/* structure with information for each extension */ +typedef struct repl_ext +{ + char *object_name; /* name of the object extended */ + int object_type; /* handle to the extended object */ + int handle; /* extension handle */ +} repl_ext; + +/* ----------------------------- Supplier ----------------------------- */ + +static repl_ext repl_sup_ext_list [REPL_EXT_ALL]; + +/* initializes replication extensions */ +void repl_sup_init_ext () +{ + int rc; + + /* populate the extension list */ + repl_sup_ext_list[REPL_SUP_EXT_OP].object_name = SLAPI_EXT_OPERATION; + + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_OPERATION, + supplier_operation_extension_constructor, + supplier_operation_extension_destructor, + &repl_sup_ext_list[REPL_SUP_EXT_OP].object_type, + &repl_sup_ext_list[REPL_SUP_EXT_OP].handle); + + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } +} + +void* repl_sup_get_ext (ext_type type, void *object) +{ + /* find the requested extension */ + repl_ext ext = repl_sup_ext_list [type]; + + void* data = slapi_get_object_extension(ext.object_type, object, ext.handle); + + return data; +} + +/* ----------------------------- Consumer ----------------------------- */ + +static repl_ext repl_con_ext_list [REPL_EXT_ALL]; + +/* initializes replication extensions */ +void repl_con_init_ext () +{ + int rc; + + /* populate the extension list */ + repl_con_ext_list[REPL_CON_EXT_OP].object_name = SLAPI_EXT_OPERATION; + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_OPERATION, + consumer_operation_extension_constructor, + consumer_operation_extension_destructor, + &repl_con_ext_list[REPL_CON_EXT_OP].object_type, + &repl_con_ext_list[REPL_CON_EXT_OP].handle); + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } + + repl_con_ext_list[REPL_CON_EXT_CONN].object_name = SLAPI_EXT_CONNECTION; + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_CONNECTION, + consumer_connection_extension_constructor, + consumer_connection_extension_destructor, + &repl_con_ext_list[REPL_CON_EXT_CONN].object_type, + &repl_con_ext_list[REPL_CON_EXT_CONN].handle); + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } + + repl_con_ext_list[REPL_CON_EXT_MTNODE].object_name = SLAPI_EXT_MTNODE; + rc = slapi_register_object_extension(repl_plugin_name, + SLAPI_EXT_MTNODE, + multimaster_mtnode_extension_constructor, + multimaster_mtnode_extension_destructor, + &repl_con_ext_list[REPL_CON_EXT_MTNODE].object_type, + &repl_con_ext_list[REPL_CON_EXT_MTNODE].handle); + if(rc!=0) + { + PR_ASSERT(0); /* JCMREPL Argh */ + } +} + +void* repl_con_get_ext (ext_type type, void *object) +{ + /* find the requested extension */ + repl_ext ext = repl_con_ext_list [type]; + + void* data = slapi_get_object_extension(ext.object_type, object, ext.handle); + + return data; +} + + diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c new file mode 100644 index 00000000..b13ad6ac --- /dev/null +++ b/ldap/servers/plugins/replication/repl_extop.c @@ -0,0 +1,1134 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "repl5_prot_private.h" +#include "cl5_api.h" + + +/* + * repl_extop.c - there are two types of functions in this file: + * - Code that implements an extended operation plugin. + * The replication DLL arranges for this code to + * be called when a StartNSDS50ReplicationRequest + * or an EndNSDS50ReplicationRequest extended operation + * is received. + * - Code that sends extended operations on an already- + * established client connection. + * + * The requestValue portion of the StartNSDS50ReplicationRequest + * looks like this: + * + * requestValue ::= SEQUENCE { + * replProtocolOID LDAPOID, + * replicatedTree LDAPDN, + supplierRUV OCTET STRING + * referralURLs SET of LDAPURL OPTIONAL + * csn OCTET STRING OPTIONAL + * } + * + */ +static int check_replica_id_uniqueness(Replica *replica, RUV *supplier_ruv); + +static int +encode_ruv (BerElement *ber, const RUV *ruv) +{ + int rc = LDAP_SUCCESS; + struct berval **bvals = NULL; + + PR_ASSERT (ber); + PR_ASSERT (ruv); + + if (ruv_to_bervals(ruv, &bvals) != 0) + { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (ber_printf(ber, "[V]", bvals) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto done; + } + + rc = LDAP_SUCCESS; + +done: + if (bvals) + ber_bvecfree (bvals); + + return rc; +} + +static struct berval * +create_NSDS50ReplicationExtopPayload(const char *protocol_oid, + const char *repl_root, char **extra_referrals, CSN *csn, + int send_end) +{ + struct berval *req_data = NULL; + BerElement *tmp_bere = NULL; + int rc = 0; + const char *csnstr = NULL; + Object *repl_obj, *ruv_obj = NULL; + Replica *repl; + RUV *ruv; + Slapi_DN *sdn; + + PR_ASSERT(protocol_oid != NULL || send_end); + PR_ASSERT(repl_root != NULL); + + /* Create the request data */ + + if ((tmp_bere = der_alloc()) == NULL) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + if (!send_end) + { + if (ber_printf(tmp_bere, "{ss", protocol_oid, repl_root) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + } + else + { + if (ber_printf(tmp_bere, "{s", repl_root) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + } + + sdn = slapi_sdn_new_dn_byref(repl_root); + repl_obj = replica_get_replica_from_dn (sdn); + if (repl_obj == NULL) + { + rc = LDAP_OPERATIONS_ERROR; + goto loser; + } + + repl = (Replica*)object_get_data (repl_obj); + PR_ASSERT (repl); + ruv_obj = replica_get_ruv (repl); + if (ruv_obj == NULL) + { + rc = LDAP_OPERATIONS_ERROR; + goto loser; + } + ruv = object_get_data(ruv_obj); + PR_ASSERT(ruv); + + /* send supplier's ruv so that consumer can build its own referrals. + In case of total protocol, it is also used as consumer's ruv once + protocol successfully completes */ + /* We need to encode and send each time the local ruv in case we have changed it */ + rc = encode_ruv (tmp_bere, ruv); + if (rc != 0) + { + goto loser; + } + + if (!send_end) + { + char s[CSN_STRSIZE]; + ReplicaId rid; + char *local_replica_referral[2] = {0}; + char **referrals_to_send = NULL; + /* Add the referral URL(s), if present */ + rid = replica_get_rid(repl); + if (!ruv_contains_replica(ruv, rid)) + { + /* + * In the event that there is no RUV component for this replica (e.g. + * if the database was just loaded from LDIF and no local CSNs have been + * generated), then we need to explicitly add this server to the list + * of referrals, since it wouldn't have been sent with the RUV. + */ + local_replica_referral[0] = (char *)multimaster_get_local_purl(); /* XXXggood had to cast away const */ + } + charray_merge(&referrals_to_send, extra_referrals, 0); + charray_merge(&referrals_to_send, local_replica_referral, 0); + if (NULL != referrals_to_send) + { + if (ber_printf(tmp_bere, "[v]", referrals_to_send) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + slapi_ch_free((void **)&referrals_to_send); + } + /* Add the CSN */ + PR_ASSERT(NULL != csn); + if (ber_printf(tmp_bere, "s", csnstr = csn_as_string(csn,PR_FALSE,s)) == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + } + + if (ber_printf(tmp_bere, "}") == -1) + { + rc = LDAP_ENCODING_ERROR; + goto loser; + } + + if (ber_flatten(tmp_bere, &req_data) == -1) + { + rc = LDAP_LOCAL_ERROR; + goto loser; + } + /* Success */ + goto done; + +loser: + /* Free stuff we allocated */ + if (NULL != req_data) + { + ber_bvfree(req_data); req_data = NULL; + } + +done: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); tmp_bere = NULL; + } + if (NULL != sdn) + { + slapi_sdn_free (&sdn); /* Put on stack instead of allocating? */ + } + if (NULL != repl_obj) + { + object_release (repl_obj); + } + if (NULL != ruv_obj) + { + object_release (ruv_obj); + } + return req_data; +} + + +struct berval * +NSDS50StartReplicationRequest_new(const char *protocol_oid, + const char *repl_root, char **extra_referrals, CSN *csn) +{ + return(create_NSDS50ReplicationExtopPayload(protocol_oid, + repl_root, extra_referrals, csn, 0)); +} + +struct berval * +NSDS50EndReplicationRequest_new(char *repl_root) +{ + return(create_NSDS50ReplicationExtopPayload(NULL, repl_root, NULL, NULL, 1)); +} + +static int +decode_ruv (BerElement *ber, RUV **ruv) +{ + int rc = -1; + struct berval **bvals = NULL; + + PR_ASSERT (ber && ruv); + + if (ber_scanf(ber, "[V]", &bvals) == -1) + { + goto done; + } + + if (ruv_init_from_bervals(bvals, ruv) != 0) + { + goto done; + } + + rc = 0; +done: + if (bvals) + ber_bvecfree (bvals); + + return rc; +} + +/* + * Decode an NSDS50 Start Replication Request extended + * operation. Returns 0 on success, -1 on decoding error. + * The caller is responsible for freeing protocol_oid, + * repl_root, referrals, and csn. + */ +static int +decode_startrepl_extop(Slapi_PBlock *pb, char **protocol_oid, char **repl_root, + RUV **supplier_ruv, char ***extra_referrals, char **csnstr) +{ + char *extop_oid = NULL; + struct berval *extop_value = NULL; + BerElement *tmp_bere = NULL; + unsigned long len; + int rc = 0; + + PR_ASSERT (pb && protocol_oid && repl_root && supplier_ruv && extra_referrals && csnstr); + + *protocol_oid = NULL; + *repl_root = NULL; + *supplier_ruv = NULL; + *extra_referrals = NULL; + *csnstr = NULL; + + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid); + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if (NULL == extop_oid || + strcmp(extop_oid, REPL_START_NSDS50_REPLICATION_REQUEST_OID) != 0 || + NULL == extop_value) + { + /* bogus */ + rc = -1; + goto free_and_return; + } + + if ((tmp_bere = ber_init(extop_value)) == NULL) + { + rc = -1; + goto free_and_return; + } + if (ber_scanf(tmp_bere, "{") == -1) + { + rc = -1; + goto free_and_return; + } + /* Get the required protocol OID and root of replicated subtree */ + if (ber_get_stringa(tmp_bere, protocol_oid) == -1) + { + rc = -1; + goto free_and_return; + } + if (ber_get_stringa(tmp_bere, repl_root) == -1) + { + rc = -1; + goto free_and_return; + } + + /* get supplier's ruv */ + if (decode_ruv (tmp_bere, supplier_ruv) == -1) + { + rc = -1; + goto free_and_return; + } + + /* Get the optional set of referral URLs */ + if (ber_peek_tag(tmp_bere, &len) == LBER_SET) + { + if (ber_scanf(tmp_bere, "[v]", extra_referrals) == -1) + { + rc = -1; + goto free_and_return; + } + } + /* Get the optional CSN */ + if (ber_peek_tag(tmp_bere, &len) == LBER_OCTETSTRING) + { + if (ber_get_stringa(tmp_bere, csnstr) == -1) + { + rc = -1; + goto free_and_return; + } + } + if (ber_scanf(tmp_bere, "}") == -1) + { + rc = -1; + goto free_and_return; + } + +free_and_return: + if (-1 == rc) + { + /* Free everything when error encountered */ + + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free ((void**)protocol_oid); + slapi_ch_free ((void**)repl_root); + slapi_ch_free ((void **)extra_referrals); + slapi_ch_free ((void**)csnstr); + + if (*supplier_ruv) + { + ruv_destroy (supplier_ruv); + } + + } + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + + return rc; +} + + +/* + * Decode an NSDS50 End Replication Request extended + * operation. Returns 0 on success, -1 on decoding error. + * The caller is responsible for freeing repl_root. + */ +static int +decode_endrepl_extop(Slapi_PBlock *pb, char **repl_root) +{ + char *extop_oid = NULL; + struct berval *extop_value = NULL; + BerElement *tmp_bere = NULL; + int rc = 0; + + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid); + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if (NULL == extop_oid || + strcmp(extop_oid, REPL_END_NSDS50_REPLICATION_REQUEST_OID) != 0 || + NULL == extop_value) + { + /* bogus */ + rc = -1; + goto free_and_return; + } + + if ((tmp_bere = ber_init(extop_value)) == NULL) + { + rc = -1; + goto free_and_return; + } + if (ber_scanf(tmp_bere, "{") == -1) + { + rc = -1; + goto free_and_return; + } + /* Get the required root of replicated subtree */ + if (ber_get_stringa(tmp_bere, repl_root) == -1) + { + rc = -1; + goto free_and_return; + } + if (ber_scanf(tmp_bere, "}") == -1) + { + rc = -1; + goto free_and_return; + } + +free_and_return: + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); + tmp_bere = NULL; + } + + return rc; +} + + + + +/* + * Decode an NSDS50ReplicationResponse extended response. + * The extended response just contains a sequence that contains: + * 1) An integer response code + * 2) An optional array of bervals representing the consumer + * replica's update vector + * Returns 0 on success, or -1 if the response could not be parsed. + */ +int +decode_repl_ext_response(struct berval *data, int *response_code, + struct berval ***ruv_bervals) +{ + BerElement *tmp_bere = NULL; + int return_value = 0; + + PR_ASSERT(NULL != response_code); + PR_ASSERT(NULL != ruv_bervals); + + if (NULL == data || NULL == response_code || NULL == ruv_bervals) + { + return_value = -1; + } + else + { + unsigned long len, tag = 0; + long temp_response_code = 0; + *ruv_bervals = NULL; + if ((tmp_bere = ber_init(data)) == NULL) + { + return_value = -1; + } + else if (ber_scanf(tmp_bere, "{e", &temp_response_code) == -1) + { + return_value = -1; + } + else if ((tag = ber_peek_tag(tmp_bere, &len)) == LBER_SEQUENCE) + { + if (ber_scanf(tmp_bere, "{V}}", ruv_bervals) == -1) + { + return_value = -1; + } + } else if (ber_scanf(tmp_bere, "}") == -1) + { + return_value = -1; + } + *response_code = (int)temp_response_code; + } + if (0 != return_value) + { + if (NULL != *ruv_bervals) + { + ber_bvecfree(*ruv_bervals); + } + } + if (NULL != tmp_bere) + { + ber_free(tmp_bere, 1); tmp_bere = NULL; + } + return return_value; +} + + +/* + * This plugin entry point is called whenever a + * StartNSDS50ReplicationRequest is received. + */ +int +multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb) +{ + int return_value = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; + int response = 0; + int rc = 0; + BerElement *resp_bere = NULL; + struct berval *resp_bval = NULL; + char *protocol_oid = NULL; + char *repl_root = NULL; + Slapi_DN *repl_root_sdn = NULL; + char **referrals = NULL; + Object *replica_object = NULL; + Replica *replica = NULL; + void *conn; + consumer_connection_extension *connext = NULL; + CSN *mycsn = NULL; + char *replicacsnstr = NULL; + CSN *replicacsn = NULL; + int zero = 0; + int one = 1; + RUV *ruv = NULL; + struct berval **ruv_bervals = NULL; + CSNGen *gen = NULL; + Object *gen_obj = NULL; + Slapi_DN *bind_sdn = NULL; + char *bind_dn = NULL; + Object *ruv_object = NULL; + RUV *supplier_ruv = NULL; + int connid, opid; + PRBool isInc = PR_FALSE; /* true if incremental update */ + char *locking_purl = NULL; /* the supplier contacting us */ + char *current_purl = NULL; /* the supplier which already has exclusive access */ + char locking_session[24]; + + /* Decode the extended operation */ + if (decode_startrepl_extop(pb, &protocol_oid, &repl_root, &supplier_ruv, + &referrals, &replicacsnstr) == -1) + { + response = NSDS50_REPL_DECODING_ERROR; + goto send_response; + } + if (NULL == protocol_oid || NULL == repl_root || NULL == replicacsnstr) + { + response = NSDS50_REPL_DECODING_ERROR; + goto send_response; + } + + connid = 0; + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + opid = 0; + slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid); + + /* + * Get a hold of the connection extension object and + * make sure it's there. + */ + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + connext = (consumer_connection_extension *)repl_con_get_ext( + REPL_CON_EXT_CONN, conn); + if (NULL == connext) + { + /* Something bad happened. Don't go any further */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + /* Verify that we know about this replication protocol OID */ + if (strcmp(protocol_oid, REPL_NSDS50_INCREMENTAL_PROTOCOL_OID) == 0) + { + /* Stash info that this is an incremental update session */ + connext->repl_protocol_version = REPL_PROTOCOL_50_INCREMENTAL; + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": Begin incremental protocol\n", + connid, opid, repl_root); + isInc = PR_TRUE; + } + else if (strcmp(protocol_oid, REPL_NSDS50_TOTAL_PROTOCOL_OID) == 0) + { + /* Stash info that this is a total update session */ + if (NULL != connext) + { + connext->repl_protocol_version = REPL_PROTOCOL_50_TOTALUPDATE; + } + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": Begin total protocol\n", + connid, opid, repl_root); + isInc = PR_FALSE; + } + else + { + /* Unknown replication protocol */ + response = NSDS50_REPL_UNKNOWN_UPDATE_PROTOCOL; + goto send_response; + } + + /* Verify that repl_root names a valid replicated area */ + if ((repl_root_sdn = slapi_sdn_new_dn_byval(repl_root)) == NULL) + { + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + /* see if this replica is being configured and wait for it */ + if (replica_is_being_configured(repl_root)) + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d replica=\"%s\": " + "Replica is being configured: try again later\n", + connid, opid, repl_root); + response = NSDS50_REPL_REPLICA_BUSY; + goto send_response; + } + + replica_object = replica_get_replica_from_dn(repl_root_sdn); + if (NULL != replica_object) + { + replica = object_get_data(replica_object); + } + if (NULL == replica) + { + response = NSDS50_REPL_NO_SUCH_REPLICA; + goto send_response; + } + + /* check that this replica is not a 4.0 consumer */ + if (replica_is_legacy_consumer (replica)) + { + response = NSDS50_REPL_LEGACY_CONSUMER; + goto send_response; + } + + /* Check that bind dn is authorized to supply replication updates */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn); /* bind_dn is allocated */ + bind_sdn = slapi_sdn_new_dn_passin(bind_dn); + if (replica_is_updatedn(replica, bind_sdn) == PR_FALSE) + { + response = NSDS50_REPL_PERMISSION_DENIED; + goto send_response; + } + + /* Check received CSN for clock skew */ + gen_obj = replica_get_csngen(replica); + if (NULL != gen_obj) + { + gen = object_get_data(gen_obj); + if (NULL != gen) + { + if (csngen_new_csn(gen, &mycsn, PR_FALSE /* notify */) == CSN_SUCCESS) + { + replicacsn = csn_new_by_string(replicacsnstr); + if (NULL != replicacsn) + { + /* ONREPL - we used to manage clock skew here. However, csn generator + code already does it. The csngen also manages local skew caused by + system clock reset, so to keep it consistent, I removed code from here */ + time_t diff = 0L; + diff = csn_time_difference(mycsn, replicacsn); + if (diff > 0) + { + /* update the state of the csn generator */ + rc = csngen_adjust_time (gen, replicacsn); + if (rc == CSN_LIMIT_EXCEEDED) /* too much skew */ + { + response = NSDS50_REPL_EXCESSIVE_CLOCK_SKEW; + goto send_response; + } + } + else if (diff <= 0) + { + /* Supplier's clock is behind ours */ + /* XXXggood check if CSN smaller than purge point */ + /* response = NSDS50_REPL_BELOW_PURGEPOINT; */ + /* goto send_response; */ + } + } + else + { + /* Oops, csnstr couldn't be converted */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + } + else + { + /* Oops, csn generator failed */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + /* update csn generator's state from the supplier's ruv */ + rc = replica_update_csngen_state (replica, supplier_ruv); /* too much skew */ + if (rc != 0) + { + response = NSDS50_REPL_EXCESSIVE_CLOCK_SKEW; + goto send_response; + } + } + else + { + /* Oops, no csn generator */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + } + else + { + /* Oops, no csn generator object */ + response = NSDS50_REPL_INTERNAL_ERROR; + goto send_response; + } + + if (check_replica_id_uniqueness(replica, supplier_ruv) != 0){ + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "Replica has same replicaID %d as supplier\n", + connid, opid, repl_root, replica_get_rid(replica)); + response = NSDS50_REPL_REPLICAID_ERROR; + goto send_response; + } + + /* Attempt to acquire exclusive access to the replicated area */ + /* Since partial URL is always the master, this locking_purl does not + * help us to know the true locker when it is a hub. Change to use + * the session's conn id and op id to identify the the supplier. + */ + /* junkrc = ruv_get_first_id_and_purl(supplier_ruv, &junkrid, &locking_purl); */ + sprintf(locking_session, "conn=%d id=%d", connid, opid); + locking_purl = &locking_session[0]; + if (replica_get_exclusive_access(replica, &isInc, connid, opid, + locking_purl, + ¤t_purl) == PR_FALSE) + { + locking_purl = NULL; /* no dangling pointers */ + response = NSDS50_REPL_REPLICA_BUSY; + goto send_response; + } + else + { + locking_purl = NULL; /* no dangling pointers */ + /* Stick the replica object pointer in the connection extension */ + connext->replica_acquired = (void *)replica_object; + replica_object = NULL; + } + + /* If this is incremental protocol get replica's ruv to return to the supplier */ + if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL) + { + ruv_object = replica_get_ruv(replica); + if (NULL != ruv_object) + { + ruv = object_get_data(ruv_object); + (void)ruv_to_bervals(ruv, &ruv_bervals); + object_release(ruv_object); + } + } + + /* + * Save the supplier ruv in the connection extension so it can + * either (a) be installed upon successful initialization (if this + * is a total update session) or used to update referral information + * for new replicas that show up in the supplier's RUV. + */ + /* + * the supplier_ruv may have been set before, so free it here + * (in ruv_copy_and_destroy) + */ + ruv_copy_and_destroy(&supplier_ruv, (RUV **)&connext->supplier_ruv); + + if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL) + { + /* The supplier ruv may have changed, so let's update the referrals */ + consumer5_set_mapping_tree_state_for_replica(replica, connext->supplier_ruv); + } + else /* full protocol */ + { + char *mtnstate = slapi_mtn_get_state(repl_root_sdn); + char **mtnreferral = slapi_mtn_get_referral(repl_root_sdn); + + /* richm 20010831 - set the mapping tree to the referral state *before* + we invoke slapi_start_bulk_import - see bug 556992 - + slapi_start_bulk_import sets the database offline, if an operation comes + in while the database is offline but the mapping tree is not referring yet, + the server gets confused + */ + /* During a total update we refer *all* operations */ + repl_set_mtn_state_and_referrals(repl_root_sdn, STATE_REFERRAL, + connext->supplier_ruv, NULL, referrals); + /* LPREPL - check the return code. + * But what do we do if mapping tree could not be updated ? */ + + /* start the bulk import */ + slapi_pblock_set (pb, SLAPI_TARGET_DN, repl_root); + rc = slapi_start_bulk_import (pb); + if (rc != LDAP_SUCCESS) + { + response = NSDS50_REPL_INTERNAL_ERROR; + /* reset the mapping tree state to what it was before + we tried to do the bulk import */ + repl_set_mtn_state_and_referrals(repl_root_sdn, mtnstate, + NULL, NULL, mtnreferral); + slapi_ch_free_string(&mtnstate); + charray_free(mtnreferral); + mtnreferral = NULL; + + goto send_response; + } + slapi_ch_free_string(&mtnstate); + charray_free(mtnreferral); + mtnreferral = NULL; + } + + response = NSDS50_REPL_REPLICA_READY; + /* Set the "is replication session" flag in the connection extension */ + slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &one ); + connext->isreplicationsession = 1; + /* Save away the connection */ + slapi_pblock_get(pb, SLAPI_CONNECTION, &connext->connection); + +send_response: + if (response != NSDS50_REPL_REPLICA_READY) + { + int resp_log_level = SLAPI_LOG_FATAL; + char purlstr[1024] = {0}; + if (current_purl) + sprintf(purlstr, " locked by %s for %s update", current_purl, + isInc ? "incremental" : "total"); + + /* Don't log replica busy as errors - these are almost always not + errors - use the replication monitoring tools to determine if + a replica is not converging, then look for pathological replica + busy errors by turning on the replication log level */ + if (response == NSDS50_REPL_REPLICA_BUSY) { + resp_log_level = SLAPI_LOG_REPL; + } + + slapi_log_error (resp_log_level, repl_plugin_name, + "conn=%d op=%d replica=\"%s\": " + "Unable to acquire replica: error: %s%s\n", + connid, opid, + (replica ? slapi_sdn_get_dn(replica_get_root(replica)) : "unknown"), + protocol_response2string (response), purlstr); + } + /* Send the response */ + if ((resp_bere = der_alloc()) == NULL) + { + /* ONREPL - not sure what we suppose to do here */ + } + ber_printf(resp_bere, "{e", response); + if (NULL != ruv_bervals) + { + ber_printf(resp_bere, "{V}", ruv_bervals); + } + ber_printf(resp_bere, "}"); + ber_flatten(resp_bere, &resp_bval); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval); + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "conn=%d op=%d repl=\"%s\": " + "StartNSDS50ReplicationRequest: response=%d rc=%d\n", + connid, opid, repl_root, + response, rc); + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + + return_value = SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + + slapi_ch_free_string(¤t_purl); + + /* protocol_oid */ + /* slapi_ch_free accepts NULL pointer */ + slapi_ch_free((void **)&protocol_oid); + + /* repl_root */ + slapi_ch_free((void **)&repl_root); + + /* supplier's ruv */ + if (supplier_ruv) + { + ruv_destroy (&supplier_ruv); + } + /* referrals */ + slapi_ch_free((void **)&referrals); + + /* replicacsnstr */ + slapi_ch_free((void **)&replicacsnstr); + + /* repl_root_sdn */ + if (NULL != repl_root_sdn) + { + slapi_sdn_free(&repl_root_sdn); + } + if (NSDS50_REPL_REPLICA_READY != response) + { + /* + * Something went wrong, and we never told the other end that the + * replica had been acquired, so we'd better release it. + */ + if (NULL != connext && NULL != connext->replica_acquired) + { + Object *r_obj = (Object*)connext->replica_acquired; + replica_relinquish_exclusive_access((Replica*)object_get_data (r_obj), + connid, opid); + } + /* Remove any flags that would indicate repl session in progress */ + if (NULL != connext) + { + connext->repl_protocol_version = REPL_PROTOCOL_UNKNOWN; + connext->isreplicationsession = 0; + } + slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &zero ); + } + /* Release reference to replica_object */ + if (NULL != replica_object) + { + object_release(replica_object); + } + /* bind_sdn */ + if (NULL != bind_sdn) + { + slapi_sdn_free(&bind_sdn); + } + /* Release reference to gen_obj */ + if (NULL != gen_obj) + { + object_release(gen_obj); + } + /* mycsn */ + if (NULL != mycsn) + { + csn_free(&mycsn); + } + /* replicacsn */ + if (NULL != replicacsn) + { + csn_free(&replicacsn); + } + /* resp_bere */ + if (NULL != resp_bere) + { + ber_free(resp_bere, 1); + } + /* resp_bval */ + if (NULL != resp_bval) + { + ber_bvfree(resp_bval); + } + /* ruv_bervals */ + if (NULL != ruv_bervals) + { + ber_bvecfree(ruv_bervals); + } + + return return_value; +} + +/* + * This plugin entry point is called whenever an + * EndNSDS50ReplicationRequest is received. + * XXXggood this code is not finished. + */ +int +multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb) +{ + int return_value = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; + char *repl_root = NULL; + BerElement *resp_bere = NULL; + struct berval *resp_bval = NULL; + int response; + void *conn; + consumer_connection_extension *connext = NULL; + int rc; + int connid=-1, opid=-1; + + /* Decode the extended operation */ + if (decode_endrepl_extop(pb, &repl_root) == -1) + { + response = NSDS50_REPL_DECODING_ERROR; + } + else + { + + /* First, verify that the current connection is a replication session */ + /* XXXggood - do we need to wait around for any pending updates to complete? + I suppose it's possible that the end request may arrive asynchronously, before + we're really done processing all the updates. + */ + /* Get a hold of the connection extension object */ + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + connext = (consumer_connection_extension *)repl_con_get_ext( + REPL_CON_EXT_CONN, conn); + if (NULL != connext && NULL != connext->replica_acquired) + { + int zero= 0; + Replica *r = (Replica*)object_get_data ((Object*)connext->replica_acquired); + + /* if this is total protocol we need to install suppliers ruv for the replica */ + if (connext->repl_protocol_version == REPL_PROTOCOL_50_TOTALUPDATE) + { + /* We no longer need to refer all operations... + * and update the referrals on the mapping tree node + */ + consumer5_set_mapping_tree_state_for_replica(r, NULL); + + /* LPREPL - First we clear the total in progress flag + Like this we know it's a normal termination of import. This is required by + the replication function that responds to backend state change. + If the flag is not clear, the callback knows that replication should not be + enabled again */ + replica_set_state_flag(r, REPLICA_TOTAL_IN_PROGRESS, PR_TRUE /* clear flag */); + + slapi_pblock_set (pb, SLAPI_TARGET_DN, repl_root); + slapi_stop_bulk_import (pb); + + /* ONREPL - this is a bit of a hack. Once bulk import is finished, + the replication function that responds to backend state change + will be called. That function normally do all ruv and changelog + processing. However, in the case of replica initalization, it + will not do the right thing because supplier does not send its + ruv tombstone to the consumer. So that's why we need to do the + second processing here. + The supplier does not send its RUV entry because it could be + more up to date then the data send to the consumer. + The best solution I think, would be to "fake" on the supplier + an entry that corresponds to the ruv sent to the consumer and then + send it as part of the data */ + + if (cl5GetState () == CL5_STATE_OPEN) + { + rc = cl5DeleteDBSync (connext->replica_acquired); + } + + replica_set_ruv (r, connext->supplier_ruv); + connext->supplier_ruv = NULL; + + /* if changelog is enabled, we need to log a dummy change for the + smallest csn in the new ruv, so that this replica ca supply + other servers. + */ + if (cl5GetState () == CL5_STATE_OPEN) + { + replica_log_ruv_elements (r); + } + + /* ONREPL code that dealt with new RUV, etc was moved into the code + that enables replication when a backend comes back online. This + code is called once the bulk import is finished */ + } + else if (connext->repl_protocol_version == REPL_PROTOCOL_50_INCREMENTAL) + { + /* The ruv from the supplier may have changed. Report the change on the + consumer side */ + + replica_update_ruv_consumer(r, connext->supplier_ruv); + } + + /* Relinquish control of the replica */ + slapi_pblock_get (pb, SLAPI_OPERATION_ID, &opid); + if (opid) slapi_pblock_get (pb, SLAPI_CONN_ID, &connid); + replica_relinquish_exclusive_access(r, connid, opid); + object_release ((Object*)connext->replica_acquired); + connext->replica_acquired = NULL; + connext->isreplicationsession= 0; + slapi_pblock_set( pb, SLAPI_CONN_IS_REPLICATION_SESSION, &zero ); + response = NSDS50_REPL_REPLICA_RELEASE_SUCCEEDED; + /* Outbound replication agreements need to all be restarted now */ + /* XXXGGOOD RESTART REEPL AGREEMENTS */ + } + } + + /* Send the response code */ + if ((resp_bere = der_alloc()) == NULL) + { + rc = LDAP_ENCODING_ERROR; + goto free_and_return; + } + ber_printf(resp_bere, "{e}", response); + ber_flatten(resp_bere, &resp_bval); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID); + slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval); + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + + return_value = SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + +free_and_return: + /* repl_root */ + slapi_ch_free((void **)&repl_root); + + /* BerElement */ + if (NULL != resp_bere) + { + ber_free(resp_bere, 1); + } + /* response */ + if (NULL != resp_bval) + { + ber_bvfree(resp_bval); + } + + return return_value; +} + +/* + * This plugin entry point is a noop entry + * point. It's used when registering extops that + * are only used as responses. We'll never receive + * one of those, unsolicited, but we still want to + * register them so they appear in the + * supportedextension attribute in the root DSE. + */ +int +extop_noop(Slapi_PBlock *pb) +{ + return SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; +} + + +static int +check_replica_id_uniqueness(Replica *replica, RUV *supplier_ruv) +{ + ReplicaId local_rid = replica_get_rid(replica); + ReplicaId sup_rid = 0; + char *sup_purl = NULL; + + if (ruv_get_first_id_and_purl(supplier_ruv, &sup_rid, &sup_purl) == RUV_SUCCESS) { + /* ReplicaID Uniqueness is checked only on Masters */ + if ((replica_get_type(replica) == REPLICA_TYPE_UPDATABLE) && + (sup_rid == local_rid)) { + return 1; + } + } + return 0; +} + + + diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c new file mode 100644 index 00000000..bee5dc4a --- /dev/null +++ b/ldap/servers/plugins/replication/repl_globals.c @@ -0,0 +1,108 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "nspr.h" +#include "repl.h" + +char *repl_plugin_name = REPL_PLUGIN_NAME; +char *repl_plugin_name_cl = REPL_PLUGIN_NAME " - changelog program"; + +/* String constants (no need to change these for I18N) */ + +#define CHANGETYPE_ADD "add" +#define CHANGETYPE_DELETE "delete" +#define CHANGETYPE_MODIFY "modify" +#define CHANGETYPE_MODRDN "modrdn" +#define CHANGETYPE_MODDN "moddn" +#define ATTR_CHANGENUMBER "changenumber" +#define ATTR_TARGETDN "targetdn" +#define ATTR_CHANGETYPE "changetype" +#define ATTR_NEWRDN "newrdn" +#define ATTR_DELETEOLDRDN "deleteoldrdn" +#define ATTR_CHANGES "changes" +#define ATTR_NEWSUPERIOR "newsuperior" +#define ATTR_CHANGETIME "changetime" +#define ATTR_DATAVERSION "dataVersion" +#define ATTR_CSN "csn" +#define TYPE_COPYINGFROM "copyingFrom" +#define TYPE_COPIEDFROM "copiedFrom" +#define FILTER_COPYINGFROM "copyingFrom=*" +#define FILTER_COPIEDFROM "copiedFrom=*" +#define FILTER_OBJECTCLASS "objectclass=*" + + +char *changetype_add = CHANGETYPE_ADD; +char *changetype_delete = CHANGETYPE_DELETE; +char *changetype_modify = CHANGETYPE_MODIFY; +char *changetype_modrdn = CHANGETYPE_MODRDN; +char *changetype_moddn = CHANGETYPE_MODDN; +char *attr_changenumber = ATTR_CHANGENUMBER; +char *attr_targetdn = ATTR_TARGETDN; +char *attr_changetype = ATTR_CHANGETYPE; +char *attr_newrdn = ATTR_NEWRDN; +char *attr_deleteoldrdn = ATTR_DELETEOLDRDN; +char *attr_changes = ATTR_CHANGES; +char *attr_newsuperior = ATTR_NEWSUPERIOR; +char *attr_changetime = ATTR_CHANGETIME; +char *attr_dataversion = ATTR_DATAVERSION; +char *attr_csn = ATTR_CSN; +char *type_copyingFrom = TYPE_COPYINGFROM; +char *type_copiedFrom = TYPE_COPIEDFROM; +char *filter_copyingFrom = FILTER_COPYINGFROM; +char *filter_copiedFrom = FILTER_COPIEDFROM; +char *filter_objectclass = FILTER_OBJECTCLASS; +char *type_cn = "cn"; +char *type_objectclass = "objectclass"; + +/* Names for replica attributes */ +const char *attr_replicaId = "nsDS5ReplicaId"; +const char *attr_replicaRoot = "nsDS5ReplicaRoot"; +const char *attr_replicaType = "nsDS5ReplicaType"; +const char *attr_replicaBindDn = "nsDS5ReplicaBindDn"; +const char *attr_state = "nsState"; +const char *attr_flags = "nsds5Flags"; +const char *attr_replicaName = "nsds5ReplicaName"; +const char *attr_replicaReferral = "nsds5ReplicaReferral"; +const char *type_ruvElement = "nsds50ruv"; +const char *type_replicaPurgeDelay = "nsds5ReplicaPurgeDelay"; +const char *type_replicaChangeCount = "nsds5ReplicaChangeCount"; +const char *type_replicaTombstonePurgeInterval = "nsds5ReplicaTombstonePurgeInterval"; +const char *type_replicaLegacyConsumer = "nsds5ReplicaLegacyConsumer"; +const char *type_ruvElementUpdatetime = "nsruvReplicaLastModified"; + +/* Attribute names for replication agreement attributes */ +const char *type_nsds5ReplicaHost = "nsds5ReplicaHost"; +const char *type_nsds5ReplicaPort = "nsds5ReplicaPort"; +const char *type_nsds5TransportInfo = "nsds5ReplicaTransportInfo"; +const char *type_nsds5ReplicaBindDN = "nsds5ReplicaBindDN"; +const char *type_nsds5ReplicaCredentials = "nsds5ReplicaCredentials"; +const char *type_nsds5ReplicaBindMethod = "nsds5ReplicaBindMethod"; +const char *type_nsds5ReplicaRoot = "nsds5ReplicaRoot"; +const char *type_nsds5ReplicatedAttributeList = "nsds5ReplicatedAttributeList"; +const char *type_nsds5ReplicaUpdateSchedule = "nsds5ReplicaUpdateSchedule"; +const char *type_nsds5ReplicaInitialize = "nsds5BeginReplicaRefresh"; +const char *type_nsds5ReplicaTimeout = "nsds5ReplicaTimeout"; +const char *type_nsds5ReplicaBusyWaitTime = "nsds5ReplicaBusyWaitTime"; +const char *type_nsds5ReplicaSessionPauseTime = "nsds5ReplicaSessionPauseTime"; + +/* To Allow Consumer Initialisation when adding an agreement - */ +const char *type_nsds5BeginReplicaRefresh = "nsds5BeginReplicaRefresh"; + +static int repl_active_threads; + +int +decrement_repl_active_threads() +{ + PR_AtomicIncrement(&repl_active_threads); + return repl_active_threads; +} + +int +increment_repl_active_threads() +{ + PR_AtomicDecrement(&repl_active_threads); + return repl_active_threads; +} diff --git a/ldap/servers/plugins/replication/repl_helper.c b/ldap/servers/plugins/replication/repl_helper.c new file mode 100644 index 00000000..05616cf1 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_helper.c @@ -0,0 +1,85 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "repl_helper.h" + +ReplGenericList * +ReplGenericListNew(void) +{ + ReplGenericList *list=NULL; + if(NULL == (list = (ReplGenericList *) + slapi_ch_calloc(1,sizeof(ReplGenericList)))) { + return(NULL); + } + list->object = NULL; + list->next = NULL; + list->prev = NULL; + return(list); +} + +void +ReplGenericListAddObject(ReplGenericList *list, + void *newObject) +{ + if(list) { + ReplGenericList *new_struct = (ReplGenericList *) + slapi_ch_calloc(1, sizeof(ReplGenericList)); + + if (!new_struct) + return; + /* set back pointer of old first element */ + if(list->next) { + list->next->prev = new_struct; + } + + /* we might have a next but since we are the first we WONT have + a previous */ + new_struct->object = newObject; + new_struct->next = list->next; + new_struct->prev = NULL; + + /* the new element is the first one */ + list->next = new_struct; + + /* if this is the only element it is the end too */ + if(NULL == list->prev) + list->prev = new_struct; + + } + return; +} + +ReplGenericList * +ReplGenericListFindObject(ReplGenericList *list, + void *object) +{ + if(!list) + return(NULL); + list = list->next; /* the first list item never has data */ + + while (list) { + if(list->object == object) + return(list); + list = list->next; + } + return(NULL); +} + +void +ReplGenericListDestroy(ReplGenericList *list, + ReplGenericListObjectDestroyFn destroyFn) +{ + ReplGenericList *list_ptr; + + while (list) { + list_ptr = list; + list = list->next; + if(destroyFn && list_ptr->object) { + (destroyFn)(list_ptr->object); + } + slapi_ch_free((void **)(&list_ptr)); + } + return; +} diff --git a/ldap/servers/plugins/replication/repl_helper.h b/ldap/servers/plugins/replication/repl_helper.h new file mode 100644 index 00000000..076710ce --- /dev/null +++ b/ldap/servers/plugins/replication/repl_helper.h @@ -0,0 +1,69 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * repl_helper.h - Helper functions (should actually be repl_utils.h) + * + * + * + */ + +#ifndef _REPL_HELPER_H +#define _REPL_HELPER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nspr.h" +#include "slapi-plugin.h" + +/* + * shamelessly stolen from the xp library + * + */ + +/* + Linked list manipulation routines + + this is a very standard linked list structure + used by many many programmers all over the world + + The lists have been modified to be doubly linked. The + first element in a list is always the header. The 'next' + pointer of the header is the first element in the list. + The 'prev' pointer of the header is the last element in + the list. + + The 'prev' pointer of the first real element in the list + is NULL as is the 'next' pointer of the last real element + in the list + + */ + + +typedef struct _repl_genericList { + void *object; + struct _repl_genericList *next; + struct _repl_genericList *prev; +} ReplGenericList; + +typedef void *(ReplGenericListObjectDestroyFn)(void *obj); + +ReplGenericList *ReplGenericListNew(void); +void ReplGenericListDestroy(ReplGenericList *list, ReplGenericListObjectDestroyFn destroyFn); + +void ReplGenericListAddObject(ReplGenericList *list, + void *newObject); +ReplGenericList *ReplGenericListFindObject(ReplGenericList *list, + void *obj); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ldap/servers/plugins/replication/repl_init.c b/ldap/servers/plugins/replication/repl_init.c new file mode 100644 index 00000000..42da2074 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_init.c @@ -0,0 +1,312 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * Add an entry like the following to dse.ldif to enable this plugin: + +dn: cn=Legacy Replication Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: Legacy Replication Plugin +nsslapd-pluginpath: /export2/servers/Hydra-supplier/lib/replication-plugin.so +nsslapd-plugininitfunc: replication_legacy_plugin_init +nsslapd-plugintype: object +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database +nsslapd-plugin-depends-on-named: Class of Service +nsslapd-plugin-depends-on-named: Multi-Master Replication Plugin +nsslapd-pluginid: replication-legacy +nsslapd-pluginversion: 5.0b1 +nsslapd-pluginvendor: Netscape Communications +nsslapd-plugindescription: Legacy Replication Plugin + +NOTE: This plugin depends on the Multi-Master Replication Plugin + +*/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "repl_shared.h" +#include "cl4.h" /* changelog interface */ +#include "dirver.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/* ----------------------------- Legacy Replication Plugin */ + +static Slapi_PluginDesc legacydesc = { "replication-legacy", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy Replication Plugin" }; +static Slapi_PluginDesc legacypreopdesc = { "replication-legacy-preop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication pre-operation plugin" }; +static Slapi_PluginDesc legacypostopdesc = { "replication-legacy-postop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication post-operation plugin" }; +static Slapi_PluginDesc legacyinternalpreopdesc = { "replication-legacy-internalpreop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication internal pre-operation plugin" }; +static Slapi_PluginDesc legacyinternalpostopdesc = { "replication-legacy-internalpostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication internal post-operation plugin" }; +static Slapi_PluginDesc legacybepostopdesc = { "replication-legacy-bepostop", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication bepost-operation plugin" }; +static Slapi_PluginDesc legacyentrydesc = { "replication-legacy-entry", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "Legacy replication entry plugin" }; + +static int legacy_stopped; /* A flag which is set when all the plugin threads are to stop */ + + +/* Initialize preoperation plugin points */ +int +legacy_preop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacypreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) legacy_preop_bind ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *) legacy_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void *) legacy_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *) legacy_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void *) legacy_preop_modrdn ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) legacy_preop_search ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void *) legacy_preop_compare ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_ENTRY_FN, (void *) legacy_pre_entry )) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_preop_init failed\n" ); + rc= -1; + } + return rc; +} + + + +/* Initialize postoperation plugin points */ +static int +legacy_postop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacypostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN, (void *) legacy_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *) legacy_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *) legacy_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *) legacy_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_postop_init failed\n" ); + rc= -1; + } + + return rc; +} + + + +/* Initialize internal preoperation plugin points (called for internal operations) */ +static int +legacy_internalpreop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyinternalpreopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN, (void *) legacy_preop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN, (void *) legacy_preop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN, (void *) legacy_preop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN, (void *) legacy_preop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_internalpreop_init failed\n" ); + rc= -1; + } + return rc; +} + + + +/* Initialize internal postoperation plugin points (called for internal operations) */ +static int +legacy_internalpostop_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyinternalpostopdesc ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *) legacy_postop_add ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *) legacy_postop_delete ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *) legacy_postop_modify ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *) legacy_postop_modrdn ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_internalpostop_init failed\n" ); + rc= -1; + } + + return rc; +} + + + +/* Initialize the entry plugin point for the legacy replication plugin */ +static int +legacy_entry_init( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + /* Set up the fn pointers for the preop and postop operations we're interested in */ + if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacyentrydesc ) != 0 ) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "legacy_entry_init failed\n" ); + rc= -1; + } + return rc; +} + + + + +/* + * Create the entry at the top of the replication configuration subtree. + */ +static int +create_config_top() +{ + const char *dn = REPL_CONFIG_TOP; + char *entry_string = slapi_ch_strdup("dn: cn=replication,cn=config\nobjectclass: top\nobjectclass: extensibleobject\ncn: replication\n"); + Slapi_PBlock *pb = slapi_pblock_new(); + Slapi_Entry *e = slapi_str2entry(entry_string, 0); + int return_value; + + slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* flags */); + slapi_add_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value); + slapi_pblock_destroy(pb); + slapi_ch_free((void **)&entry_string); + return return_value; +} + + +/* Start the legacy replication plugin */ +static int +legacy_start( Slapi_PBlock *pb ) +{ + static int legacy_started = 0; + int rc= 0; /* OK */ + + if (!legacy_started) + { + int ctrc; + + /* Initialise support for cn=monitor */ + repl_monitor_init(); + + /* Initialise support for "" (the rootdse) */ + /* repl_rootdse_init(); */ + + /* Decode the command line args to see if we're dumping to LDIF */ + { + int argc; + char **argv; + slapi_pblock_get( pb, SLAPI_ARGC, &argc); + slapi_pblock_get( pb, SLAPI_ARGV, &argv); + repl_entry_init(argc,argv); + } + + /* Create the entry at the top of the config area, if it doesn't exist */ + /* XXXggood this should be in the 5.0 plugin! */ + ctrc = create_config_top(); + if (ctrc != LDAP_SUCCESS && ctrc != LDAP_ALREADY_EXISTS) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable to " + "create configuration entry %s: %s\n", REPL_CONFIG_TOP, + ldap_err2string(ctrc)); + } + (void)legacy_consumer_config_init(); + + /* register to be notified when backend state changes */ + slapi_register_backend_state_change((void *)legacy_consumer_be_state_change, + legacy_consumer_be_state_change); + + legacy_started = 1; + legacy_stopped = 0; + } + return rc; +} + + +/* Post-start function for the legacy replication plugin */ +static int +legacy_poststart( Slapi_PBlock *pb ) +{ + int rc = 0; /* OK */ + return rc; +} + + +/* Stop the legacy replication plugin */ +static int +legacy_stop( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + + if (!legacy_stopped) + { + /*csnShutdown();*/ + legacy_stopped = 1; + } + + /* unregister backend state change notification */ + slapi_unregister_backend_state_change((void *)legacy_consumer_be_state_change); + + return rc; +} + + +/* Initialize the legacy replication plugin */ +int +replication_legacy_plugin_init(Slapi_PBlock *pb) +{ + static int legacy_initialised= 0; + int rc= 0; /* OK */ + void *identity = NULL; + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &identity); + PR_ASSERT (identity); + repl_set_plugin_identity (PLUGIN_LEGACY_REPLICATION, identity); + + if(config_is_slapd_lite()) + { + slapi_log_error( SLAPI_LOG_FATAL, repl_plugin_name, + "replication plugin not approved for restricted" + " mode Directory Server.\n" ); + rc= -1; + } + if(rc==0 && !legacy_initialised) + { + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&legacydesc ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) legacy_start ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) legacy_stop ); + rc= slapi_pblock_set( pb, SLAPI_PLUGIN_POSTSTART_FN, (void *) legacy_poststart ); + + /* Register the plugin interfaces we implement */ + rc= slapi_register_plugin("preoperation", 1 /* Enabled */, "legacy_preop_init", legacy_preop_init, "Legacy replication preoperation plugin", NULL, identity); + rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "legacy_postop_init", legacy_postop_init, "Legacy replication postoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "legacy_internalpreop_init", legacy_internalpreop_init, "Legacy replication internal preoperation plugin", NULL, identity); + rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "legacy_internalpostop_init", legacy_internalpostop_init, "Legacy replication internal postoperation plugin", NULL, identity); + rc= slapi_register_plugin("entry", 1 /* Enabled */, "legacy_entry_init", legacy_entry_init, "Legacy replication entry plugin", NULL, identity); + + legacy_initialised= 1; + } + return rc; +} + + +int +get_legacy_stop() +{ + return legacy_stopped; +} diff --git a/ldap/servers/plugins/replication/repl_modify.c b/ldap/servers/plugins/replication/repl_modify.c new file mode 100644 index 00000000..26753cc0 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_modify.c @@ -0,0 +1,29 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +/* The modify operation plugin functions for the legacy replication plugin */ + +int +legacy_preop_modify( Slapi_PBlock *pb ) +{ + return legacy_preop( pb, "legacy_preop_modify", OP_MODIFY ); +} + +int +legacy_bepreop_modify( Slapi_PBlock *pb ) +{ + int rc= 0; /* OK */ + return rc; +} + +int +legacy_postop_modify( Slapi_PBlock *pb ) +{ + return legacy_postop( pb, "legacy_postop_modify", OP_MODIFY ); +} diff --git a/ldap/servers/plugins/replication/repl_modrdn.c b/ldap/servers/plugins/replication/repl_modrdn.c new file mode 100644 index 00000000..653aff0a --- /dev/null +++ b/ldap/servers/plugins/replication/repl_modrdn.c @@ -0,0 +1,28 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +/* The modrdn plugin points for the legacy replication plugin */ + +int +legacy_preop_modrdn( Slapi_PBlock *pb ) +{ + return legacy_preop(pb, "legacy_preop_modrdn", OP_MODDN); +} + +int +legacy_bepreop_modrdn( Slapi_PBlock *pb ) +{ + return 0; /* OK */ +} + +int +legacy_postop_modrdn( Slapi_PBlock *pb ) +{ + return legacy_postop(pb, "legacy_postop_modrdn", OP_MODDN); +} diff --git a/ldap/servers/plugins/replication/repl_monitor.c b/ldap/servers/plugins/replication/repl_monitor.c new file mode 100644 index 00000000..ec52d611 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_monitor.c @@ -0,0 +1,58 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <string.h> + +#include "repl.h" +#include "slapi-plugin.h" + +/* Forward Declartions */ +static int repl_monitor_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +int +repl_monitor_init() +{ + /* The FE DSE *must* be initialised before we get here */ + int return_value= LDAP_SUCCESS; + static int initialized = 0; + + if (!initialized) + { + /* ONREPL - this is commented until we implement 4.0 style changelog + slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"cn=monitor",LDAP_SCOPE_BASE,"(objectclass=*)",repl_monitor_search,NULL); */ + initialized = 1; + } + + return return_value; +} + +static int +repl_monitor_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + const char *sdv = get_server_dataversion(); + if ( sdv != NULL ) + { + int port; + char buf[BUFSIZ]; + struct berval val; + struct berval *vals[2]; + vals[0] = &val; + vals[1] = NULL; + port= config_get_port(); + if(port==0) + { + port= config_get_secureport(); + } + /* ONREPL - how do we publish changenumbers now with multiple changelogs? + sprintf( buf, "%s:%lu %s% lu", get_localhost_DNS(), port, sdv, ldapi_get_last_changenumber()); + */ + val.bv_val = buf; + val.bv_len = strlen( buf ); + slapi_entry_attr_replace( e, attr_dataversion, vals ); + } + return SLAPI_DSE_CALLBACK_OK; +} + diff --git a/ldap/servers/plugins/replication/repl_objset.c b/ldap/servers/plugins/replication/repl_objset.c new file mode 100644 index 00000000..f0a68097 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_objset.c @@ -0,0 +1,524 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* repl_objset.c */ +/* + * Support for lifetime management of sets of objects. + * Objects are refcounted. NOTE: this API is deprecated. + * Use the object/objset API provided by libslapd. + */ + +#include "slapi-plugin.h" +#include "slapi-private.h" +#include "repl_objset.h" +#include <prlock.h> + +#define REPL_OBJSET_OBJ_FLAG_DELETED 0x1 + + +typedef struct repl_objset_object +{ + void *data; /* pointer to actual node data */ + char *key; /* key for this object. null-terminated string */ + int refcnt; /* reference count for this object */ + unsigned long flags; /* state of this object */ +} Repl_Objset_object; + +typedef struct repl_objset +{ + LList *objects; + FNFree destructor; /* destructor for objects - provided by caller */ + PRLock *lock; +} repl_objset; + + +/* Forward declarations */ +static void removeObjectNolock(Repl_Objset *o, Repl_Objset_object *co); +static Repl_Objset_object *removeCurrentObjectAndGetNextNolock (Repl_Objset *o, + Repl_Objset_object *co, void *iterator); + +/* + * Create a new set. + * + * Arguments: + * destructor: a function to be called when an object is to be destroyed + * + * Returns: + * A pointer to the object set, or NULL if an error occured. + */ +Repl_Objset * +repl_objset_new(FNFree destructor) +{ + Repl_Objset *p; + + p = (Repl_Objset *)slapi_ch_malloc(sizeof(Repl_Objset)); + p->lock = PR_NewLock(); + if (NULL == p->lock) + { + free(p); p = NULL; + } + p->objects = llistNew(); + p->destructor = destructor; + return p; +} + + +/* + * Destroy a Repl_Objset. + * Arguments: + * o: the object set to be destroyed + * maxwait: the maximum time to wait for all object refcnts to + * go to zero. + * panic_fn: a function to be called if, after waiting "maxwait" + * seconds, not all object refcnts are zero. + * The caller must ensure that no one else holds references to the + * set or any objects it contains. + */ +void +repl_objset_destroy(Repl_Objset **o, time_t maxwait, FNFree panic_fn) +{ + Repl_Objset_object *co = NULL; + time_t now, stop_time; + int really_gone; + int loopcount; + void *cookie; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != *o); + + time(&now); + stop_time = now + maxwait; + + /* + * Loop over the objects until they all are actually gone, + * or until maxwait seconds have passed. + */ + really_gone = 0; + loopcount = 0; + + while (now < stop_time) + { + void *cookie; + + PR_Lock((*o)->lock); + + if ((co = llistGetFirst((*o)->objects, &cookie)) == NULL) + { + really_gone = 1; + PR_Unlock((*o)->lock); + break; + } + while (NULL != co) + { + /* Set the deleted flag so the object isn't returned by iterator */ + co->flags |= REPL_OBJSET_OBJ_FLAG_DELETED; + if (0 == co->refcnt) + { + /* Remove the object */ + co = removeCurrentObjectAndGetNextNolock ((*o), co, cookie); + + } + else + co = llistGetNext((*o)->objects, &cookie); + } + PR_Unlock((*o)->lock); + time(&now); + if (loopcount > 0) + { + DS_Sleep(PR_TicksPerSecond()); + } + loopcount++; + } + + if (!really_gone) + { + if (NULL != panic_fn) + { + /* + * Call the "aargh, this thing won't go away" panic + * function for each remaining object. + */ + PR_Lock((*o)->lock); + if ((co = llistGetFirst((*o)->objects, &cookie)) == NULL) + { + panic_fn(co->data); + while (NULL != co) + { + panic_fn(co->data); + co = llistGetNext((*o)->objects, &cookie); + } + } + PR_Unlock((*o)->lock); + } + } + else + { + /* Free the linked list */ + llistDestroy(&(*o)->objects, (*o)->destructor); + PR_DestroyLock((*o)->lock); + free(*o); *o = NULL; + } +} + + + +/* + * Add an object to an object set. + * + * Arguments: + * o: The object set to which the object is to be added. + * name: a null-terminated string that names the object. Must + * be unique. + * obj: pointer to the object to be added. + * + * Return codes: + * REPL_OBJSET_SUCCESS: the item was added to the object set + * REPL_OBJSET_DUPLICATE_KEY: an item with the same key is already + * in the object set. + * REPL_OBJSET_INTERNAL_ERROR: something bad happened. + */ +int +repl_objset_add(Repl_Objset *o, const char *name, void *obj) +{ + Repl_Objset_object *co = NULL; + Repl_Objset_object *tmp = NULL; + int rc = REPL_OBJSET_SUCCESS; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != name); + PR_ASSERT(NULL != obj); + + PR_Lock(o->lock); + tmp = llistGet(o->objects, name); + if (NULL != tmp) + { + rc = REPL_OBJSET_DUPLICATE_KEY; + goto loser; + } + co = (Repl_Objset_object *)slapi_ch_malloc(sizeof(Repl_Objset_object)); + co->data = obj; + co->key = slapi_ch_strdup(name); + co->refcnt = 0; + co->flags = 0UL; + if (llistInsertHead(o->objects, name, co) != 0) + { + rc = REPL_OBJSET_INTERNAL_ERROR; + goto loser; + } + PR_Unlock(o->lock); + return rc; + +loser: + PR_Unlock(o->lock); + if (NULL != co) + { + if (NULL != co->key) + { + slapi_ch_free((void **)&co->key); + } + slapi_ch_free((void **)&co); + } + return rc; +} + + +/* Must be called with the repl_objset locked */ +static void +removeObjectNolock(Repl_Objset *o, Repl_Objset_object *co) +{ + /* Remove from list */ + llistRemove(o->objects, co->key); + /* Destroy the object */ + o->destructor(&(co->data)); + free(co->key); + /* Deallocate the container */ + free(co); +} + +static Repl_Objset_object * +removeCurrentObjectAndGetNextNolock (Repl_Objset *o, Repl_Objset_object *co, void *iterator) +{ + Repl_Objset_object *ro; + + PR_ASSERT (o); + PR_ASSERT (co); + PR_ASSERT (iterator); + + ro = llistRemoveCurrentAndGetNext (o->objects, &iterator); + + o->destructor(&(co->data)); + free(co->key); + /* Deallocate the container */ + free(co); + + return ro; +} + +/* Must be called with the repl_objset locked */ +static void +acquireNoLock(Repl_Objset_object *co) +{ + co->refcnt++; +} + + +/* Must be called with the repl_objset locked */ +static void +releaseNoLock(Repl_Objset *o, Repl_Objset_object *co) +{ + PR_ASSERT(co->refcnt >= 1); + if (--co->refcnt == 0) + { + if (co->flags & REPL_OBJSET_OBJ_FLAG_DELETED) + { + /* Remove the object */ + removeObjectNolock(o, co); + } + } +} + +/* + * Retrieve an object from the object set. If an object with + * the given key is found, its reference count is incremented, + * a pointer to the object is returned, and a handle to use + * to refer to the object is returned. + * + * Arguments: + * o: The object set to be searched. + * key: key of the object to be retrieved + * obj: pointer to void * that will be set to point to the + * object, if found. + * handle: pointer to void * that will be set to point to a + * handle, used to refer to the object, if found. + * + * Returns: + * REPL_OBJSET_SUCCESS: an item was found. + * REPL_OBJSET_KEY_NOT_FOUND: no item with the given key was found. + */ +int +repl_objset_acquire(Repl_Objset *o, const char *key, void **obj, void **handle) +{ + Repl_Objset_object *co = NULL; + int rc = REPL_OBJSET_KEY_NOT_FOUND; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != key); + PR_ASSERT(NULL != obj); + PR_ASSERT(NULL != handle); + + PR_Lock(o->lock); + co = llistGet(o->objects, key); + if (NULL != co && !(co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)) + { + acquireNoLock(co); + *obj = (void *)co->data; + *handle = (void *)co; + rc = REPL_OBJSET_SUCCESS; + } + PR_Unlock(o->lock); + return rc; +} + + +/* + * Return an object to the object set. + * + * Arguments: + * o: The object set containing the objct + * handle: reference to the object. + * + */ +void +repl_objset_release(Repl_Objset *o, void *handle) +{ + Repl_Objset_object *co; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != handle); + + co = (Repl_Objset_object *)handle; + PR_Lock(o->lock); + releaseNoLock(o, co); + PR_Unlock(o->lock); +} + + + +/* + * Delete an object from the object set + * + * Arguments: + * o: The object set containing the object. + * handle: reference to the object. + */ +void +repl_objset_delete(Repl_Objset *o, void *handle) +{ + Repl_Objset_object *co = (Repl_Objset_object *)handle; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != co); + + PR_Lock(o->lock); + if (co->refcnt == 0) + { + removeObjectNolock(o, co); + } + else + { + /* Set deleted flag, clean up later */ + co->flags |= REPL_OBJSET_OBJ_FLAG_DELETED; + } + PR_Unlock(o->lock); +} + + +typedef struct _iterator +{ + Repl_Objset *o; /* set for which iterator was created */ + void *cookie; /* for linked list */ + Repl_Objset_object *co; /* our wrapper */ +} iterator; + +/* + * Get the first object in an object set. + * Used when enumerating all of the objects in a set. + * Arguments: + * o: The object set being enumerated + * itcontext: an iteration context, to be passed back to subsequent calls + * to repl_objset_next_object. + * handle: a pointer to pointer to void. This will be filled in with + * a reference to the object's enclosing object. + * Returns: + * A pointer to the next object in the set, or NULL if there are no + * objects in the set. + * + */ +void * +repl_objset_first_object(Repl_Objset *o, void **itcontext, void **handle) +{ + Repl_Objset_object *co = NULL; + void *cookie; + void *retptr = NULL; + iterator *it; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != itcontext); + + *itcontext = NULL; + + if (NULL == o->objects) { + return(NULL); + } + + /* Find the first non-deleted object */ + PR_Lock(o->lock); + co = llistGetFirst(o->objects, &cookie); + while (NULL != co && (co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)) + { + co = llistGetNext(o->objects, &cookie); + } + + if (NULL != co) + { + /* Increment refcnt until item given back to us */ + acquireNoLock(co); + + /* Save away context */ + it = (iterator *)slapi_ch_malloc(sizeof(iterator)); + *itcontext = it; + it->o = o; + it->cookie = cookie; + it->co = co; + retptr = co->data; + } + + PR_Unlock(o->lock); + if (NULL != handle) + { + *handle = co; + } + + return retptr; +} + + + +/* + * Get the next object in the set. + * Arguments: + * o: The object set being enumerated + * itcontext: an iteration context, to be passed back to subsequent calls + * to repl_objset_next_object. + * handle: a pointer to pointer to void. This will be filled in with + * a reference to the object's enclosing object. + * + * Returns: + * A pointer to the next object in the set, or NULL if there are no more + * objects. + */ +void * +repl_objset_next_object(Repl_Objset *o, void *itcontext, void **handle) +{ + Repl_Objset_object *co = NULL; + Repl_Objset_object *tmp_co; + void *retptr = NULL; + iterator *it = (iterator *)itcontext; + + PR_ASSERT(NULL != o); + PR_ASSERT(NULL != it); + PR_ASSERT(NULL != it->co); + + PR_Lock(o->lock); + tmp_co = it->co; + + /* Find the next non-deleted object */ + while ((co = llistGetNext(o->objects, &it->cookie)) != NULL && + !(co->flags & REPL_OBJSET_OBJ_FLAG_DELETED)); + + if (NULL != co) + { + acquireNoLock(co); + it->co = co; + retptr = co->data; + } + else + { + /* + * No more non-deleted objects - erase context (freeing + * it is responsibility of caller. + */ + it->cookie = NULL; + it->co = NULL; + } + releaseNoLock(o, tmp_co); + PR_Unlock(o->lock); + if (NULL != handle) + { + *handle = co; + } + return retptr; +} + + + +/* + * Destroy an itcontext iterator + */ +void +repl_objset_iterator_destroy(void **itcontext) +{ + if (NULL != itcontext && NULL != *itcontext) + { + /* check if we did not iterate through the entire list + and need to release last accessed element */ + iterator *it = *(iterator**)itcontext; + if (it->co) + repl_objset_release (it->o, it->co); + + slapi_ch_free((void **)itcontext); + } +} diff --git a/ldap/servers/plugins/replication/repl_objset.h b/ldap/servers/plugins/replication/repl_objset.h new file mode 100644 index 00000000..72af5109 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_objset.h @@ -0,0 +1,37 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + */ + +/* repl_objset.h */ + /* + * Support for lifetime management of sets of objects. + * Objects are refcounted. NOTE: This API should go away + * in favor of the objset API provided by libslapd. + */ +#ifndef _REPL_OBJSET_H +#define __REPL_OBJSET_H + +#include "llist.h" + +#define REPL_OBJSET_SUCCESS 0 +#define REPL_OBJSET_DUPLICATE_KEY 1 +#define REPL_OBJSET_INTERNAL_ERROR 2 +#define REPL_OBJSET_KEY_NOT_FOUND 3 + +typedef struct repl_objset Repl_Objset; + +Repl_Objset *repl_objset_new(FNFree destructor); +void repl_objset_destroy(Repl_Objset **o, time_t maxwait, FNFree panic_fn); +int repl_objset_add(Repl_Objset *o, const char *name, void *obj); +int repl_objset_acquire(Repl_Objset *o, const char *key, void **obj, void **handle); +void repl_objset_release(Repl_Objset *o, void *handle); +void repl_objset_delete(Repl_Objset *o, void *handle); +void *repl_objset_next_object(Repl_Objset *o, void *cookie, void **handle); +void *repl_objset_first_object(Repl_Objset *o, void **cookie, void **handle); +void repl_objset_iterator_destroy(void **itcontext); + +#endif /* _REPL_OBJSET_H */ diff --git a/ldap/servers/plugins/replication/repl_opext.c b/ldap/servers/plugins/replication/repl_opext.c new file mode 100644 index 00000000..d9c8d1ed --- /dev/null +++ b/ldap/servers/plugins/replication/repl_opext.c @@ -0,0 +1,97 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* supplier_operation_extension.c - replication extension to the Operation object + */ + + +#include "repl.h" +#include "repl5.h" + +/* ***** Supplier side ***** */ + +/* JCMREPL -> PINAKIxxx The interface to the referral stuff is not correct */ +void ref_array_dup_free(void *the_copy); /* JCMREPL - should be #included */ + +/* supplier operation extension constructor */ +void* supplier_operation_extension_constructor (void *object, void *parent) +{ + supplier_operation_extension *ext = (supplier_operation_extension*) slapi_ch_calloc (1, sizeof (supplier_operation_extension)); + if (ext == NULL) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication supplier operation extension - out of memory\n" ); + } + else + { + ext->prevent_recursive_call= 0; + } + return ext; +} + +/* supplier operation extension destructor */ +void supplier_operation_extension_destructor (void *ext,void *object, void *parent) +{ + if (ext) + { + supplier_operation_extension *supext = (supplier_operation_extension *)ext; + if (supext->operation_parameters) + operation_parameters_free (&(supext->operation_parameters)); + if (supext->repl_gen) + slapi_ch_free ((void**)&supext->repl_gen); + slapi_ch_free((void **)&ext); + } +} + +/* ***** Consumer side ***** */ + +/* consumer operation extension constructor */ +void* consumer_operation_extension_constructor (void *object, void *parent) +{ + consumer_operation_extension *ext = (consumer_operation_extension*) slapi_ch_calloc (1, sizeof (consumer_operation_extension)); + if (ext == NULL) + { + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "unable to create replication consumer operation extension - out of memory\n" ); + } + if(object!=NULL && parent!=NULL) + { + consumer_connection_extension *connext; + connext = (consumer_connection_extension *)repl_con_get_ext(REPL_CON_EXT_CONN, parent); + if(NULL != connext) + { + /* We copy the Connection Replicated Session flag to the Replicated Operation flag */ + if (connext->isreplicationsession) + { + operation_set_flag((Slapi_Operation *)object,OP_FLAG_REPLICATED); + } + /* We set the Replication DN flag if session bound as replication dn */ + if (connext->is_legacy_replication_dn) + { + operation_set_flag((Slapi_Operation *)object, OP_FLAG_LEGACY_REPLICATION_DN); + } + } + } + else + { + /* (parent==NULL) for internal operations */ + PR_ASSERT(object!=NULL); + } + + return ext; +} + +/* consumer operation extension destructor */ +void consumer_operation_extension_destructor (void *ext,void *object, void *parent) +{ + if (NULL != ext) + { + consumer_operation_extension *opext = (consumer_operation_extension *)ext; + if (NULL != opext->search_referrals) + { + ref_array_dup_free(opext->search_referrals); /* JCMREPL - undefined */ + opext->search_referrals = NULL; + } + slapi_ch_free((void **)&ext); + } +} diff --git a/ldap/servers/plugins/replication/repl_ops.c b/ldap/servers/plugins/replication/repl_ops.c new file mode 100644 index 00000000..e1e51355 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_ops.c @@ -0,0 +1,180 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" + +int +legacy_postop( Slapi_PBlock *pb, const char *caller, int operation_type) +{ + int rc = 0; + Object *r_obj; + Replica *r; + + r_obj = replica_get_replica_for_op (pb); + if (r_obj == NULL) /* there is no replica configured for this operations */ + return 0; + else + { + /* check if this replica is 4.0 consumer */ + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + /* this replica is not a 4.0 consumer - so we don't need to do any processing */ + if (!replica_is_legacy_consumer (r)) + { + object_release (r_obj); + return 0; + } + + object_release (r_obj); + } + + slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &rc); + if (0 == rc) + { + if (OP_ADD == operation_type || OP_MODIFY == operation_type) + { + void *op; + consumer_operation_extension *opext = NULL; + + /* Optimise out traversal of mods/entry if no cop{ied|ying}From present */ + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, op); + if (NULL != opext && opext->has_cf) + { + process_legacy_cf( pb ); + } + } + } + + return 0; +} + + + +static char *not_replicationdn_errmsg = + "An operation was submitted that contained copiedFrom or " + "copyingFrom attributes, but the connection was not bound " + "as the replicationdn."; + +int +legacy_preop(Slapi_PBlock *pb, const char *caller, int operation_type) +{ + int rc = 0; + Slapi_Operation *operation = NULL; + consumer_operation_extension *opext = NULL; + int has_cf = 0; + Object *r_obj; + Replica *r; + int is_legacy_op = 0; + + slapi_pblock_get( pb, SLAPI_OPERATION, &operation ); + is_legacy_op = operation_is_flag_set(operation,OP_FLAG_LEGACY_REPLICATION_DN); + r_obj = replica_get_replica_for_op (pb); + + if (r_obj == NULL) { /* there is no replica configured for this operations */ + if (is_legacy_op){ + /* This is a legacy replication operation but there are NO replica defined + Just refuse it */ + slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, + "Replication operation refused because the consumer is not defined as a replica", 0, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Incoming replication operation was refused because " + "there's no replica defined for this operation\n"); + return -1; + } + else { + return 0; + } + } + else + { + /* check if this replica is 4.0 consumer */ + r = (Replica*)object_get_data (r_obj); + PR_ASSERT (r); + + if (!replica_is_legacy_consumer (r)) + { + object_release (r_obj); + if (is_legacy_op) { + /* This is a legacy replication operation + but the replica is doesn't accept from legacy + Just refuse it */ + slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, + "Replication operation refused because " + "the consumer is not defined as a legacy replica", 0, NULL); + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "Incoming replication operation was refused because " + "there's no legacy replica defined for this operation\n"); + return -1; + } else { + return 0; + } + } + + object_release (r_obj); + } + + opext = (consumer_operation_extension*) repl_con_get_ext (REPL_CON_EXT_OP, operation); + + switch (operation_type) { + case OP_ADD: + { + Slapi_Entry *e = NULL; + Slapi_Attr *attr; + /* + * Check if the entry being added has copiedFrom/copyingFrom + * attributes. + */ + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + if (NULL != e) + { + if (slapi_entry_attr_find(e, type_copiedFrom, &attr) == 0) + { + has_cf = 1; + } + else + if (slapi_entry_attr_find(e, type_copyingFrom, &attr) == 0) + { + has_cf = 1; + } + } + /* JCMREPL - If this is a replicated operation then the baggage control also contains the Unique Identifier of the superior entry. */ + } + break; + case OP_MODIFY: + { + LDAPMod **mods = NULL; + int i; + + /* + * Check if the modification contains copiedFrom/copyingFrom + * attributes. + */ + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (i = 0; NULL != mods && NULL != mods[i]; i++) + { + if ((strcasecmp(mods[i]->mod_type, type_copiedFrom) == 0) || + (strcasecmp(mods[i]->mod_type, type_copyingFrom) == 0)) + { + has_cf = 1; + } + } + } + break; + case OP_DELETE: + break; + case OP_MODDN: + break; + } + + /* Squirrel away an optimization hint for the postop plugin */ + opext->has_cf = has_cf; + + return rc; +} diff --git a/ldap/servers/plugins/replication/repl_rootdse.c b/ldap/servers/plugins/replication/repl_rootdse.c new file mode 100644 index 00000000..2bd1d8e8 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_rootdse.c @@ -0,0 +1,79 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <string.h> + +#include "repl.h" +#include "cl4.h" +#include "slapi-plugin.h" + +/* Forward Declartions */ +static int repl_rootdse_search (Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg); + +int +repl_rootdse_init() +{ + /* The FE DSE *must* be initialised before we get here */ + int return_value= LDAP_SUCCESS; + + slapi_config_register_callback(SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,"",LDAP_SCOPE_BASE,"(objectclass=*)",repl_rootdse_search,NULL); + + return return_value; +} + +static int +repl_rootdse_search(Slapi_PBlock *pb, Slapi_Entry* e, Slapi_Entry* entryAfter, int *returncode, char *returntext, void *arg) +{ + +#if 0 + struct berval val; + struct berval *vals[2]; + vals[0] = &val; + vals[1] = NULL; + + /* machine data suffix */ + val.bv_val = REPL_CONFIG_TOP; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, ATTR_NETSCAPEMDSUFFIX, vals ); + + /* Changelog information */ +/* ONREPL because we now support multiple 4.0 changelogs we no longer publish + info in the rootdse */ + if ( get_repl_backend() != NULL ) + { + char buf[BUFSIZ]; + changeNumber cnum; + + /* Changelog suffix */ + val.bv_val = changelog4_get_suffix (); + if ( val.bv_val != NULL ) + { + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "changelog", vals ); + } + slapi_ch_free ((void **)&val.bv_val); + + /* First change number contained in log */ + cnum = ldapi_get_first_changenumber(); + sprintf( buf, "%lu", cnum ); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "firstchangenumber", vals ); + + /* Last change number contained in log */ + cnum = ldapi_get_last_changenumber(); + sprintf( buf, "%lu", cnum ); + val.bv_val = buf; + val.bv_len = strlen( val.bv_val ); + slapi_entry_attr_replace( e, "lastchangenumber", vals ); + } +#endif + + return SLAPI_DSE_CALLBACK_OK; +} + + + diff --git a/ldap/servers/plugins/replication/repl_search.c b/ldap/servers/plugins/replication/repl_search.c new file mode 100644 index 00000000..cfa21222 --- /dev/null +++ b/ldap/servers/plugins/replication/repl_search.c @@ -0,0 +1,25 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "slapi-plugin.h" +#include "repl.h" + +/* XXXggood I think we no longer need this - the mapping tree should do it for us */ +int +legacy_preop_search( Slapi_PBlock *pb ) +{ + int return_code = 0; + return return_code; +} + + +/* XXXggood I think we no longer need this - the mapping tree should do it for us */ +int +legacy_pre_entry( Slapi_PBlock *pb ) +{ + int return_code = 0; + return return_code; +} diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h new file mode 100644 index 00000000..0c7454ab --- /dev/null +++ b/ldap/servers/plugins/replication/repl_shared.h @@ -0,0 +1,132 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* repl_shared.h - definitions shared between 4.0 and 5.0 replication + modules + */ + +#ifndef REPL_SHARED_H +#define REPL_SHARED_H + +#include "slapi-private.h" +#include "slapi-plugin.h" +#include "ldif.h" /* GGOODREPL - is this cheating? */ + +#ifdef _WIN32 +#define FILE_PATHSEP '\\' +#else +#define FILE_PATHSEP '/' +#endif + +#define CHANGELOGDB_TRIM_INTERVAL 300 /* 5 minutes */ + +#define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir" +#define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries" +#define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage" +/* Changelog Internal Configuration Parameters -> DB related */ +#define CONFIG_CHANGELOG_DB_DBCACHESIZE "nsslapd-dbcachesize" +#define CONFIG_CHANGELOG_DB_DURABLE_TRANSACTIONS "nsslapd-db-durable-transaction" +#define CONFIG_CHANGELOG_DB_CHECKPOINT_INTERVAL "nsslapd-db-checkpoint-interval" +#define CONFIG_CHANGELOG_DB_CIRCULAR_LOGGING "nsslapd-db-circular-logging" +#define CONFIG_CHANGELOG_DB_PAGE_SIZE "nsslapd-db-page-size" +#define CONFIG_CHANGELOG_DB_LOGFILE_SIZE "nsslapd-db-logfile-size" +#define CONFIG_CHANGELOG_DB_MAXTXN_SIZE "nsslapd-db-max-txn" +#define CONFIG_CHANGELOG_DB_VERBOSE "nsslapd-db-verbose" +#define CONFIG_CHANGELOG_DB_DEBUG "nsslapd-db-debug" +#define CONFIG_CHANGELOG_DB_TRICKLE_PERCENTAGE "nsslapd-db-trickle-percentage" +#define CONFIG_CHANGELOG_DB_SPINCOUNT "nsslapd-db-spin-count" +/* Changelog Internal Configuration Parameters -> Changelog Cache related */ +#define CONFIG_CHANGELOG_CACHESIZE "nsslapd-cachesize" +#define CONFIG_CHANGELOG_CACHEMEMSIZE "nsslapd-cachememsize" +#define CONFIG_CHANGELOG_NB_LOCK "nsslapd-db-locks" +#define CONFIG_CHANGELOG_MAX_CONCURRENT_WRITES "nsslapd-changelogmaxconcurrentwrites" + +#define T_CHANGETYPESTR "changetype" +#define T_CHANGETYPE 1 +#define T_TIMESTR "time" +#define T_TIME 2 +#define T_DNSTR "dn" +#define T_DN 3 +#define T_CHANGESTR "change" +#define T_CHANGE 4 + +#define T_ADDCTSTR "add" +#define T_ADDCT 4 +#define T_MODIFYCTSTR "modify" +#define T_MODIFYCT 5 +#define T_DELETECTSTR "delete" +#define T_DELETECT 6 +#define T_MODRDNCTSTR "modrdn" +#define T_MODRDNCT 7 +#define T_MODDNCTSTR "moddn" +#define T_MODDNCT 8 + +#define T_MODOPADDSTR "add" +#define T_MODOPADD 9 +#define T_MODOPREPLACESTR "replace" +#define T_MODOPREPLACE 10 +#define T_MODOPDELETESTR "delete" +#define T_MODOPDELETE 11 +#define T_MODSEPSTR "-" +#define T_MODSEP 12 + +#define T_NEWRDNSTR "newrdn" +#define T_NEWSUPERIORSTR ATTR_NEWSUPERIOR +#define T_DRDNFLAGSTR "deleteoldrdn" + +#define T_ERR -1 +#define AWAITING_OP -1 + +#define STATE_REFERRAL "referral" +#define STATE_UPDATE_REFERRAL "referral on update" +#define STATE_BACKEND "backend" + +#define REPL_PLUGIN_NAME "NSMMReplicationPlugin" +/* + * Changed version from 1.0 to 2.0 when we switched from libdb32 to libdb33 + * richm 20020708 + * also changed name from REPL_PLUGIN_VERSION to CHANGELOG_DB_VERSION since we use + * a different version for the plugin itself and this particular version is only + * used for the changelog database +*/ +/* + * Changed version from 2.0 to 3.0 when we switched from libdb33 to libdb41 + * noriko 20021203 + */ +#define CHANGELOG_DB_VERSION_PREV "3.0" +#define CHANGELOG_DB_VERSION "4.0" +extern char *repl_plugin_name; +extern char *repl_plugin_name_cl; + +/* repl_monitor.c */ +int repl_monitor_init(); + +/* In replutil.c */ +char ** get_cleattrs(); +unsigned long strntoul( char *from, size_t len, int base ); +void freepmods( LDAPMod **pmods ); +char *copy_berval (struct berval* from); +void entry_print(Slapi_Entry *e); +int copyfile(char* source, char *destination, int overwrite, int mode); +time_t age_str2time (const char *age); +const char* changeType2Str (int type); +int str2ChangeType (const char *str); +lenstr *make_changes_string(LDAPMod **ldm, char **includeattrs); +Slapi_Mods* parse_changes_string(char *str); +PRBool IsValidOperation (const slapi_operation_parameters *op); +const char *map_repl_root_to_dbid(Slapi_DN *repl_root); +PRBool is_ruv_tombstone_entry (Slapi_Entry *e); + +/* replication plugins */ +enum { + PLUGIN_LEGACY_REPLICATION, + PLUGIN_MULTIMASTER_REPLICATION, + PLUGIN_MAX +}; + +void* repl_get_plugin_identity (int pluginID); +void repl_set_plugin_identity (int pluginID, void *identity); + +#endif diff --git a/ldap/servers/plugins/replication/replication.def b/ldap/servers/plugins/replication/replication.def new file mode 100644 index 00000000..e71be4f6 --- /dev/null +++ b/ldap/servers/plugins/replication/replication.def @@ -0,0 +1,16 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7.0 Replication Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + plugin_init_debug_level @1 + replication_legacy_plugin_init @2 + replication_multimaster_plugin_init @3 + repl_chain_on_update @4 diff --git a/ldap/servers/plugins/replication/replutil.c b/ldap/servers/plugins/replication/replutil.c new file mode 100644 index 00000000..f8a23f93 --- /dev/null +++ b/ldap/servers/plugins/replication/replutil.c @@ -0,0 +1,1073 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + + /* + * replutil.c - various utility functions common to all replication methods. + */ + +#include <nspr.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <errno.h> +#ifndef _WIN32 +#include <sys/file.h> +#include <sys/socket.h> +#include <unistd.h> +#include <fcntl.h> +#endif +#ifdef OS_solaris +#include <dlfcn.h> /* needed for dlopen and dlsym */ +#endif /* solaris: dlopen */ +#include <time.h> +#ifdef LINUX +#include <errno.h> /* weird use of errno */ +#endif + +#include "slapi-plugin.h" +#include "repl5.h" +#include "repl.h" + +typedef int (*open_fn)(const char *path, int flags, ...); + +/* this is set during replication plugin initialization from the plugin entry */ +static char *replpluginpath = NULL; +static PRBool is_chain_on_update_setup(const Slapi_DN *replroot); + +/* + * All standard changeLogEntry attributes (initialized in get_cleattrs) + */ +static char *cleattrs[ 10 ] = { NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL }; + +/* + * Function: get_cleattrs + * + * Returns: an array of pointers to attribute names. + * + * Arguments: None. + * + * Description: Initializes, if necessary, and returns an array of char *s + * with attribute names used for retrieving changeLogEntry + * entries from the directory. + */ +char ** +get_cleattrs() +{ + if ( cleattrs[ 0 ] == NULL ) { + cleattrs[ 0 ] = type_objectclass; + cleattrs[ 1 ] = attr_changenumber; + cleattrs[ 2 ] = attr_targetdn; + cleattrs[ 3 ] = attr_changetype; + cleattrs[ 4 ] = attr_newrdn; + cleattrs[ 5 ] = attr_deleteoldrdn; + cleattrs[ 6 ] = attr_changes; + cleattrs[ 7 ] = attr_newsuperior; + cleattrs[ 8 ] = attr_changetime; + cleattrs[ 9 ] = NULL; + } + return cleattrs; +} + +/* + * Function: add_bval2mods + * + * Description: same as add_val2mods, but sticks in a bval instead. + * val can be null. + */ +void +add_bval2mods(LDAPMod **mod, char *type, char *val, int mod_op) +{ + *mod = (LDAPMod *) slapi_ch_calloc(1, sizeof (LDAPMod)); + memset (*mod, 0, sizeof(LDAPMod)); + (*mod)->mod_op = mod_op | LDAP_MOD_BVALUES; + (*mod)->mod_type = slapi_ch_strdup(type); + + if (val != NULL){ + (*mod)->mod_bvalues = (struct berval **) slapi_ch_calloc(2, sizeof(struct berval *)); + (*mod)->mod_bvalues[0] = (struct berval *) slapi_ch_malloc (sizeof(struct berval)); + (*mod)->mod_bvalues[1] = NULL; + (*mod)->mod_bvalues[0]->bv_len = strlen(val); + (*mod)->mod_bvalues[0]->bv_val = slapi_ch_strdup(val); + } else { + (*mod)->mod_bvalues = NULL; + } +} + + +char* +copy_berval (struct berval* from) +{ + char* s = slapi_ch_malloc (from->bv_len + 1); + memcpy (s, from->bv_val, from->bv_len); + s [from->bv_len] = '\0'; + return s; +} + + +/* + * Function: entry_print + * Arguments: e - entry to print + * Returns: nothing + * Description: Prints the contents of an Slapi_Entry struct. Used for debugging. + */ +void +entry_print( Slapi_Entry *e ) +{ + int sz; + char *p; + + printf( "Slapi_Entry dump:\n" ); + + if ( e == NULL ) { + printf( "Slapi_Entry is NULL\n" ); + return; + } + + if (( p = slapi_entry2str( e, &sz )) == NULL ) { + printf( "slapi_entry2str returned NULL\n" ); + return; + } + puts( p ); + fflush( stdout ); + free( p ); + return; +} + +/* NSPR supports large file, but, according to dboreham, it does not work. + The backed has its own functions to deal with large files. I thought + about making them slapi function, but I don't think it makes sense because + server should only export function which have to do with its operation + and copying files is not one of them. So, instead, I made a copy of it in the + replication module. I will switch it to NSPR once that stuff works. +*/ + +int copyfile(char* source, char * destination, int overwrite, int mode) +{ +#if defined _WIN32 + return (0 == CopyFile(source,destination,overwrite ? FALSE : TRUE)); +#else +#ifdef DB_USE_64LFS +#define OPEN_FUNCTION dblayer_open_large +#else +#define OPEN_FUNCTION open +#endif + int source_fd = -1; + int dest_fd = -1; + char *buffer = NULL; + int return_value = -1; + int bytes_to_write = 0; + + /* malloc the buffer */ + buffer = (char*) malloc(64*1024); + if (NULL == buffer) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "copy file: memory allocation failed\n"); + goto error; + } + /* Open source file */ + source_fd = OPEN_FUNCTION(source,O_RDONLY,0); + if (-1 == source_fd) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "copyfile: failed to open source file %s\n", source); + goto error; + } + /* Open destination file */ + dest_fd = OPEN_FUNCTION(destination,O_CREAT | O_WRONLY, mode); + if (-1 == dest_fd) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "copyfile: failed to open destination file %s\n", destination); + goto error; + } + /* Loop round reading data and writing it */ + while (1) + { + return_value = read(source_fd,buffer,64*1024); + if (return_value <= 0) + { + /* means error or EOF */ + break; + } + bytes_to_write = return_value; + return_value = write(dest_fd,buffer,bytes_to_write); + if (return_value != bytes_to_write) + { + /* means error */ + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "copyfile: failed to write to destination file %s\n"); + return_value = -1; + break; + } + } +error: + if (source_fd != -1) + { + close(source_fd); + } + if (dest_fd != -1) + { + close(dest_fd); + } + if (NULL != buffer) + { + free(buffer); + } + return return_value; +#endif +} + +/* convert time from string like 1h (1 hour) to corresponding time in seconds */ +time_t +age_str2time (const char *age) +{ + char *maxage; + char unit; + time_t ageval; + + if (age == NULL || age[0] == '\0' || strcmp (age, "0") == 0) + { + return 0; + } + + maxage = slapi_ch_strdup ( age ); + unit = maxage[ strlen( maxage ) - 1 ]; + maxage[ strlen( maxage ) - 1 ] = '\0'; + ageval = strntoul( maxage, strlen( maxage ), 10 ); + if ( maxage) + { + slapi_ch_free ( (void **) &maxage ); + } + switch ( unit ) + { + case 's': + break; + case 'm': + ageval *= 60; + break; + case 'h': + ageval *= ( 60 * 60 ); + break; + case 'd': + ageval *= ( 24 * 60 * 60 ); + break; + case 'w': + ageval *= ( 7 * 24 * 60 * 60 ); + break; + default: + slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, + "age_str2time: unknown unit \"%c\" " + "for maxiumum changelog age\n", unit ); + ageval = -1; + } + + return ageval; +} + +const char* +changeType2Str (int type) +{ + switch (type) + { + case T_ADDCT: return T_ADDCTSTR; + case T_MODIFYCT: return T_MODIFYCTSTR; + case T_MODRDNCT: return T_MODRDNCTSTR; + case T_DELETECT: return T_DELETECTSTR; + default: return NULL; + } +} + +int +str2ChangeType (const char *str) +{ + if (strcasecmp (str, T_ADDCTSTR) == 0) + return T_ADDCT; + + if (strcasecmp (str, T_MODIFYCTSTR) == 0) + return T_MODIFYCT; + + if (strcasecmp (str, T_MODRDNCTSTR) == 0) + return T_MODRDNCT; + + if (strcasecmp (str, T_DELETECTSTR) == 0) + return T_DELETECT; + + return -1; +} + +lenstr * +make_changes_string(LDAPMod **ldm, char **includeattrs) +{ + lenstr *l; + int i, j, len; + int skip; + + /* loop through the LDAPMod struct and construct the changes attribute */ + l = lenstr_new(); + + for ( i = 0; ldm[ i ] != NULL; i++ ) { + /* If a list of explicit attributes was given, only add those */ + if ( NULL != includeattrs ) { + skip = 1; + for ( j = 0; includeattrs[ j ] != NULL; j++ ) { + if ( strcasecmp( includeattrs[ j ], ldm[ i ]->mod_type ) == 0 ) { + skip = 0; + break; + } + } + if ( skip ) { + continue; + } + } + switch ( ldm[ i ]->mod_op & ~LDAP_MOD_BVALUES ) { + case LDAP_MOD_ADD: + addlenstr( l, "add: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + case LDAP_MOD_DELETE: + addlenstr( l, "delete: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + case LDAP_MOD_REPLACE: + addlenstr( l, "replace: " ); + addlenstr( l, ldm[ i ]->mod_type ); + addlenstr( l, "\n" ); + break; + } + for ( j = 0; ldm[ i ]->mod_bvalues != NULL && + ldm[ i ]->mod_bvalues[ j ] != NULL; j++ ) { + char *buf = NULL; + char *bufp = NULL; + + len = strlen( ldm[ i ]->mod_type ); + len = LDIF_SIZE_NEEDED( len, + ldm[ i ]->mod_bvalues[ j ]->bv_len ) + 1; + buf = slapi_ch_malloc( len ); + bufp = buf; + ldif_put_type_and_value( &bufp, ldm[ i ]->mod_type, + ldm[ i ]->mod_bvalues[ j ]->bv_val, + ldm[ i ]->mod_bvalues[ j ]->bv_len ); + *bufp = '\0'; + + addlenstr( l, buf ); + + free( buf ); + } + addlenstr( l, "-\n" ); + } + return l; +} + +/* note that the string get modified by ldif_parse*** functions */ +Slapi_Mods * +parse_changes_string(char *str) +{ + int rc; + Slapi_Mods *mods; + Slapi_Mod mod; + char *line, *next; + char *type, *value; + int vlen; + struct berval bv; + + /* allocate mods array */ + mods = slapi_mods_new (); + if (mods == NULL) + return NULL; + + slapi_mods_init (mods, 16); /* JCMREPL - ONREPL : 16 bigger than needed? */ + + /* parse mods */ + next = str; + line = ldif_getline (&next); + while (line) + { + slapi_mod_init (&mod, 0); + while (line) + { + char * errmsg = NULL; + + if (strcasecmp (line, "-") == 0) + { + if (slapi_mod_isvalid (&mod)) + { + slapi_mods_add_smod (mods, &mod); + /* JCMREPL - ONREPL - slapi_mod_done(&mod) ??? */ + } + else + { + /* ONREPL - need to cleanup */ + } + + line = ldif_getline (&next); + break; + } + + rc = ldif_parse_line(line, &type, &value, &vlen, &errmsg); + if (rc != 0) + { + /* ONREPL - log warning */ + if ( errmsg != NULL ) { + slapi_log_error( SLAPI_LOG_PARSE, repl_plugin_name, "%s", errmsg ); + slapi_ch_free( (void**)&errmsg ); + } + slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name, + "Failed to parse the ldif line.\n"); + continue; + } + + if (strcasecmp (type, "add") == 0) + { + slapi_mod_set_operation (&mod, LDAP_MOD_ADD | LDAP_MOD_BVALUES); + } + else if (strcasecmp (type, "delete") == 0) + { + slapi_mod_set_operation (&mod, LDAP_MOD_DELETE | LDAP_MOD_BVALUES); + } + else if (strcasecmp (type, "replace") == 0) + { + slapi_mod_set_operation (&mod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES); + } + else /* attr: value pair */ + { + /* adding first value */ + if (slapi_mod_get_type (&mod) == NULL) + { + slapi_mod_set_type (&mod, type); + } + + bv.bv_val = value; + bv.bv_len = vlen; + + slapi_mod_add_value (&mod, &bv); + } + + line = ldif_getline (&next); + } + } + + return mods; +} + +static void* g_plg_identity [PLUGIN_MAX]; + +void* +repl_get_plugin_identity (int pluginID) +{ + PR_ASSERT (pluginID < PLUGIN_MAX); + return g_plg_identity [pluginID]; +} + +void +repl_set_plugin_identity (int pluginID, void *identity) +{ + PR_ASSERT (pluginID < PLUGIN_MAX); + g_plg_identity [pluginID] = identity; +} + +/* this function validates operation parameters */ +PRBool +IsValidOperation (const slapi_operation_parameters *op) +{ + if (op == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL operation\n"); + return PR_FALSE; + } + + if (op->csn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL operation CSN\n"); + return PR_FALSE; + } + + if (op->target_address.uniqueid == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL entry uniqueid\n"); + return PR_FALSE; + } + + if (op->target_address.dn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL entry DN\n"); + return PR_FALSE; + } + + switch (op->operation_type) + { + case SLAPI_OPERATION_ADD: if (op->p.p_add.target_entry == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL entry for add operation\n"); + return PR_FALSE; + } + else + break; + + case SLAPI_OPERATION_MODIFY: if (op->p.p_modify.modify_mods == NULL || + op->p.p_modify.modify_mods[0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL mods for modify operation\n"); + return PR_FALSE; + } + else + break; + + case SLAPI_OPERATION_MODRDN: if (op->p.p_modrdn.modrdn_mods == NULL || + op->p.p_modrdn.modrdn_mods[0] == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL mods for modrdn operation\n"); + return PR_FALSE; + } + if (op->p.p_modrdn.modrdn_newrdn == NULL) + { + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, + "IsValidOperation: NULL new rdn for modrdn operation\n"); + return PR_FALSE; + } + else + break; + + case SLAPI_OPERATION_DELETE: break; + + default: return PR_FALSE; + } + + return PR_TRUE; +} + + + +const char * +map_repl_root_to_dbid(Slapi_DN *repl_root) +{ + const char *return_ptr; + + PR_ASSERT(NULL != repl_root); + if (NULL != repl_root) + { + /* XXXggood get per-database ID here, when code available */ + } + return_ptr = get_server_dataversion(); /* XXXggood temporary hack until we have per-database instance dbids */ + return return_ptr; +} + + + +PRBool +is_ruv_tombstone_entry (Slapi_Entry *e) +{ + char *dn; + char *match; + PR_ASSERT (e); + + dn = slapi_entry_get_dn (e); + PR_ASSERT (dn); + + /* tombstone has rdn: nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff */ + match = strstr (dn, RUV_STORAGE_ENTRY_UNIQUEID); + + return (match != NULL); +} + +LDAPControl* create_managedsait_control () +{ + LDAPControl *control; + + control = (LDAPControl*)slapi_ch_malloc (sizeof (LDAPControl)); + + control->ldctl_oid = slapi_ch_strdup (LDAP_CONTROL_MANAGEDSAIT); + control->ldctl_value.bv_val = NULL; + control->ldctl_value.bv_len = 0; + control->ldctl_iscritical = '\0'; + + return control; +} + +LDAPControl* create_backend_control (Slapi_DN *sdn) +{ + LDAPControl *control = NULL; + const char *be_name = slapi_mtn_get_backend_name(sdn); + if (NULL != be_name) { + control = (LDAPControl*)slapi_ch_malloc (sizeof (LDAPControl)); + + control->ldctl_oid = slapi_ch_strdup ("2.16.840.1.113730.3.4.14"); + control->ldctl_value.bv_val = strdup(be_name); + control->ldctl_value.bv_len = strlen (be_name); + control->ldctl_iscritical = 1; + } + + return control; +} + +/* + * HREF_CHAR_ACCEPTABLE was copied from slapd/referral.c + * which was copied from libldap/tmplout.c. + */ +/* Note: an identical function is in ../../slapd/referral.c */ +#define HREF_CHAR_ACCEPTABLE( c ) (( c >= '-' && c <= '9' ) || \ + ( c >= '@' && c <= 'Z' ) || \ + ( c == '_' ) || \ + ( c >= 'a' && c <= 'z' )) + +/* + * Function: strcat_escaped + * + * Returns: nothing + * + * Description: Appends string s2 to s1, URL-escaping (%HH) unsafe + * characters in s2 as appropriate. This function was + * copied from slapd/referral.c. + * which was copied from libldap/tmplout.c. + * added const qualifier + * + * Author: MCS + */ +/* + * append s2 to s1, URL-escaping (%HH) unsafe characters + */ +/* Note: an identical function is in ../../slapd/referral.c */ +static void +strcat_escaped( char *s1, const char *s2 ) +{ + char *p, *q; + char *hexdig = "0123456789ABCDEF"; + + p = s1 + strlen( s1 ); + for ( q = (char*)s2; *q != '\0'; ++q ) { + if ( HREF_CHAR_ACCEPTABLE( *q )) { + *p++ = *q; + } else { + *p++ = '%'; + *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ]; + *p++ = hexdig[ 0x0F & *q ]; + } + } + + *p = '\0'; +} + +/* + This function appends the replication root to the purl referrals found + in the given ruv and the other given referrals, merges the lists, and sets the + referrals in the mapping tree node corresponding to the given sdn, which is the + repl_root + This function also sets the mapping tree state (e.g. disabled, backend, referral, + referral on update) - the mapping tree has very specific rules about how states + can be set in the presence of referrals - specifically: + 1) the nsslapd-referral attribute must be set before changing the state to referral + or referral on update + 2) the state must be set to backend or disabled before removing referrals +*/ +void +repl_set_mtn_state_and_referrals( + const Slapi_DN *repl_root_sdn, + const char *mtn_state, + const RUV *ruv, + char **ruv_referrals, + char **other_referrals +) +{ + int rc = 0; + int ii = 0; + char **referrals_to_set = NULL; + PRBool chain_on_update = is_chain_on_update_setup(repl_root_sdn); + + /* Fix for blackflag bug 601440: We want the new behaviour of DS, + ** going forward, to now be that if the nsds5replicareferral attrib + ** has values, it should be the only values in nsslapd-referral (as + ** opposed to older behaviour of concatenating with RUV-based + ** referrals). -jay@netscape.com + */ + if (other_referrals) { + /* use the referrals passed in, instead of RUV-based referrals */ + charray_merge(&referrals_to_set, other_referrals, 1); + /* Do copies. referrals is freed at the end */ + } + else + { + /* use the referrals from the RUV */ + ruv_referrals= (ruv ? ruv_get_referrals(ruv) : ruv_referrals); + if (ruv_referrals) { + charray_merge(&referrals_to_set, ruv_referrals, 1); + if (ruv) /* free referrals from ruv_get_referrals() */ + charray_free(ruv_referrals); + } + } + + /* next, add the repl root dn to each referral if not present */ + for (ii = 0; referrals_to_set && referrals_to_set[ii]; ++ii) { + struct ldap_url_desc *lud = NULL; + int myrc = ldap_url_parse(referrals_to_set[ii], &lud); + /* see if the dn is already in the referral URL */ + if (myrc == LDAP_URL_ERR_NODN || !lud || !lud->lud_dn) { + /* add the dn */ + int len = strlen(referrals_to_set[ii]); + const char *cdn = slapi_sdn_get_dn(repl_root_sdn); + char *tmpref = NULL; + int need_slash = 0; + if (referrals_to_set[ii][len-1] != '/') { + len++; /* add another one for the slash */ + need_slash = 1; + } + len += (strlen(cdn) * 3) + 2; /* 3 for %HH possible per char */ + tmpref = slapi_ch_malloc(len); + sprintf(tmpref, "%s%s", referrals_to_set[ii], (need_slash ? "/" : "")); + strcat_escaped(tmpref, cdn); + slapi_ch_free((void **)&referrals_to_set[ii]); + referrals_to_set[ii] = tmpref; + } + if (lud) + ldap_free_urldesc(lud); + } + + if (!referrals_to_set) { /* deleting referrals */ + /* Set state before */ + if (!chain_on_update) { + slapi_mtn_set_state(repl_root_sdn, (char *)mtn_state); + } + /* We should delete referral only if we want to set the + replica database in backend state mode */ + /* if chain on update mode, go ahead and set the referrals anyway */ + if (strcasecmp(mtn_state, STATE_BACKEND) == 0 || chain_on_update) { + rc = slapi_mtn_set_referral(repl_root_sdn, referrals_to_set); + if (rc == LDAP_NO_SUCH_ATTRIBUTE) { + /* we will get no such attribute (16) if we try to set the referrals to NULL if + there are no referrals - not an error */ + rc = LDAP_SUCCESS; + } + } + } else { /* Replacing */ + rc = slapi_mtn_set_referral(repl_root_sdn, referrals_to_set); + if (rc == LDAP_SUCCESS && !chain_on_update){ + slapi_mtn_set_state(repl_root_sdn, (char *)mtn_state); + } + } + + if (rc != LDAP_SUCCESS) { + char ebuf[BUFSIZ]; + slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "repl_set_mtn_referrals: could " + "not set referrals for replica %s: %d\n", + escape_string(slapi_sdn_get_dn(repl_root_sdn), ebuf), rc); + } + + charray_free(referrals_to_set); + return; +} + +/* + * This function allows to use a local backend in conjunction with + * a chaining backend + * The local ldbm backend is the replication consumer database + * (e.g. on a hub or consumer) - it is read-only except for supplier updates + * The chaining backend points to the supplier(s) + * This distribution logic forwards the update request to the chaining + * backend, and sends the search request to the local ldbm database + * + * To be able to use it one must define one ldbm backend and one chaining + * backend in the mapping tree node - the ldbm backend will usually + * already be there + * + */ +int +repl_chain_on_update(Slapi_PBlock *pb, Slapi_DN * target_dn, + char **mtn_be_names, int be_count, + Slapi_DN * node_dn, int *mtn_be_states) +{ + char * requestor_dn; + unsigned long op_type; + Slapi_Operation *op; + int repl_op = 0; + int local_backend = -1; /* index of local backend */ + int chaining_backend = -1; /* index of chain backend */ + PRBool local_online = PR_FALSE; /* true if the local db is online */ + PRBool chain_online = PR_FALSE; /* true if the chain db is online */ + int ii; + int opid, connid; + + slapi_pblock_get(pb, SLAPI_CONN_ID, &connid); + slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid); + /* first, we have to decide which backend is the local backend + * and which is the chaining one + * also find out if any are not online (e.g. during import) + */ + for (ii = 0; ii < be_count; ++ii) + { + Slapi_Backend *be = slapi_be_select_by_instance_name(mtn_be_names[ii]); + if (slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA)) + { + chaining_backend = ii; + if (mtn_be_states[ii] == SLAPI_BE_STATE_ON) + { + chain_online = PR_TRUE; + } + } + else + { + local_backend = ii; + if (mtn_be_states[ii] == SLAPI_BE_STATE_ON) + { + local_online = PR_TRUE; + } + } +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d be " + "%s is the %s backend and is %s\n", + connid, opid, + mtn_be_names[ii], (chaining_backend == ii) ? "chaining" : "local", + (mtn_be_states[ii] == SLAPI_BE_STATE_ON) ? "online" : "offline"); +*/ + } + + /* if no chaining backends are defined, just use the local one */ + if (chaining_backend == -1) { + return local_backend; + } + + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* All internal operations go to the local backend */ + if (operation_is_flag_set(op, OP_FLAG_INTERNAL)) { + return local_backend; + } + + /* Check the operation type + * read-only operation will go to the local backend if online + */ + op_type = slapi_op_get_type(op); + if (local_online && + ((op_type == SLAPI_OPERATION_SEARCH) || + (op_type == SLAPI_OPERATION_BIND) || + (op_type == SLAPI_OPERATION_UNBIND) || + (op_type == SLAPI_OPERATION_COMPARE))) { +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d op is " + "%d: using local backend\n", + connid, opid, op_type); +*/ + return local_backend; + } + + /* if the operation is done by directory manager + * use local database even for updates because it is an administrative + * operation + * remarks : one could also use an update DN in the same way + * to let update operation go to the local backend when they are done + * by specific administrator user but let all the other user + * go to the read-write replica + * also - I don't think it is possible to chain directory manager + */ + slapi_pblock_get(pb, SLAPI_REQUESTOR_DN, &requestor_dn); + if (slapi_dn_isroot(requestor_dn)) { +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d requestor " + "is root: using local backend\n", connid, opid); +*/ + return local_backend; + } + + /* if the operation is a replicated operation + * use local database even for updates to avoid infinite loops + */ + slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &repl_op); + if (repl_op) { +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d op is " + "replicated: using local backend\n", connid, opid); +*/ + return local_backend; + } + + /* all other case (update while not directory manager) : + * or any normal non replicated client operation while local is disabled (import) : + * use the chaining backend + */ +/* + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "repl_chain_on_update: conn=%d op=%d using " + "chaining backend\n", connid, opid); +*/ + return chaining_backend; +} + +int +repl_enable_chain_on_update(Slapi_DN *suffix) +{ + /* Submit a Modify operation to add the distribution function to the mapping tree + node for the given suffix */ + slapi_mods smods; + Slapi_Operation *op = NULL; + int operation_result; + Slapi_PBlock *pb= slapi_pblock_new(); + char *mtnnodedn; + + slapi_mods_init(&smods,2); + + /* need path and file name of the replication plugin here */ + slapi_mods_add_string(&smods, LDAP_MOD_ADD, "nsslapd-distribution-plugin", replpluginpath); + slapi_mods_add_string(&smods, LDAP_MOD_ADD, "nsslapd-distribution-funct", "repl_chain_on_update"); + + /* need DN of mapping tree node here */ + mtnnodedn = slapi_get_mapping_tree_node_configdn(suffix); + slapi_modify_internal_set_pb( + pb, + mtnnodedn, + slapi_mods_get_ldapmods_byref(&smods), /* JCM cast */ + NULL, /*Controls*/ + NULL, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + + slapi_modify_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result); + slapi_ch_free_string(&mtnnodedn); + slapi_pblock_destroy(pb); + switch(operation_result) + { + case LDAP_SUCCESS: + /* OK, everything is fine. */ + break; + default: + PR_ASSERT(0); /* JCMREPL FOR DEBUGGING */ + } + slapi_mods_done(&smods); + + return operation_result; +} + +int +repl_disable_chain_on_update(Slapi_DN *suffix) +{ + /* Submit a Modify operation to remove the distribution function from the mapping tree + node for the given suffix */ + slapi_mods smods; + Slapi_Operation *op = NULL; + int operation_result; + Slapi_PBlock *pb= slapi_pblock_new(); + char *mtnnodedn; + + slapi_mods_init(&smods,2); + + slapi_mods_add_modbvps(&smods, LDAP_MOD_DELETE, "nsslapd-distribution-plugin", NULL); + slapi_mods_add_modbvps(&smods, LDAP_MOD_DELETE, "nsslapd-distribution-funct", NULL); + + /* need DN of mapping tree node here */ + mtnnodedn = slapi_get_mapping_tree_node_configdn(suffix); + slapi_modify_internal_set_pb( + pb, + mtnnodedn, + slapi_mods_get_ldapmods_byref(&smods), /* JCM cast */ + NULL, /*Controls*/ + NULL, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + + slapi_modify_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result); + slapi_ch_free_string(&mtnnodedn); + slapi_pblock_destroy(pb); + switch(operation_result) + { + case LDAP_SUCCESS: + /* OK, everything is fine. */ + break; + default: + PR_ASSERT(0); /* JCMREPL FOR DEBUGGING */ + } + slapi_mods_done(&smods); + + return operation_result; +} + +static PRBool +is_chain_on_update_setup(const Slapi_DN *replroot) +{ + /* Do an internal search of the mapping tree node to see if chain on update is setup + for this replica + - has two backends + - has a distribution function + - has a distribution plugin + - one of the backends is a ldbm database + - one of the backends is a chaining database + */ + static char* attrs[] = { "nsslapd-backend", + "nsslapd-distribution-plugin", "nsslapd-distribution-funct", + NULL }; + int operation_result; + Slapi_PBlock *pb= slapi_pblock_new(); + char *mtnnodedn = slapi_get_mapping_tree_node_configdn(replroot); + PRBool retval = PR_FALSE; + + slapi_search_internal_set_pb( + pb, + mtnnodedn, + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, /*attrs*/ + 0, /*attrsonly*/ + NULL, /*Controls*/ + NULL, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + slapi_search_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &operation_result); + switch(operation_result) + { + case LDAP_SUCCESS: + { + Slapi_Entry **entries= NULL; + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if(entries!=NULL && entries[0]!=NULL) + { + Slapi_Entry *e = entries[0]; + + char **backends = slapi_entry_attr_get_charray(e, "nsslapd-backend"); + char *plg = slapi_entry_attr_get_charptr(e, "nsslapd-distribution-plugin"); + char *func = slapi_entry_attr_get_charptr(e, "nsslapd-distribution-funct"); + + if (backends && backends[0] && backends[1] && plg && func) + { + /* all the necessary attrs are present - check to see if we + have one chaining backend */ + Slapi_Backend *be0 = slapi_be_select_by_instance_name(backends[0]); + Slapi_Backend *be1 = slapi_be_select_by_instance_name(backends[1]); + PRBool foundchain0 = slapi_be_is_flag_set(be0,SLAPI_BE_FLAG_REMOTE_DATA); + PRBool foundchain1 = slapi_be_is_flag_set(be1,SLAPI_BE_FLAG_REMOTE_DATA); + retval = (foundchain0 || foundchain1) && + !(foundchain0 && foundchain1); /* 1 (but not both) backend is chaining */ + } + slapi_ch_array_free(backends); + slapi_ch_free_string(&plg); + slapi_ch_free_string(&func); + } + else /* could not find mapping tree entry - assume not set up */ + { + } + } + break; + default: /* search error - assume not set up */ + break; + } + slapi_ch_free_string(&mtnnodedn); + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + + return retval; +} + +void +repl_set_repl_plugin_path(const char *path) +{ + replpluginpath = slapi_ch_strdup(path); +} diff --git a/ldap/servers/plugins/replication/tests/dnp_sim.c b/ldap/servers/plugins/replication/tests/dnp_sim.c new file mode 100644 index 00000000..ff524988 --- /dev/null +++ b/ldap/servers/plugins/replication/tests/dnp_sim.c @@ -0,0 +1,1033 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dnp_simulation.c - this file varifies the correctness of dnp algorithm + by generating random sequences of operations, applying + the algorithm and outputing the result + + usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>] + -h - print usage information. + -n <number of simulations> - how many simulations to perform; default - 1. + -v - verbose mode (prints full entry state after each operation execution) + -f <output file> - file where results are stored; by default results are + printed to the screen. + -o <op file> - file that contains operation sequence to execute; by default, + random sequence is generated. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <memory.h> +#include <string.h> +#include <time.h> + +#define MAX_OPS 12 /* maximum number of operations in a simulation */ +#define MAX_VALS 10 /* maximum number of values is entry or dn */ +#define NOT_PRESENT -1 + +/* data types */ +typedef struct value_state +{ + int value_index; /* value */ + int presense_csn; /* last time at which we know the value was present */ + int distinguished_csn; /* last time at which we know the value was distinguished */ + int delete_csn; /* last attempt to delete this value */ + int non_distinguished_csns [MAX_OPS];/* list of times at which value became non-distinguished */ + int present; /* flag that tells whether the value iscurrently present */ +} Value_State; + +typedef struct entry_state +{ + int dn_index; + int dn_csn; + Value_State values[MAX_VALS]; /* values of the attribute */ + int attr_delete_csn; /* last attempt to delete the entire attribute */ +} Entry_State; + +typedef enum +{ + OP_ADD_VALUE, + OP_RENAME_ENTRY, + OP_DELETE_VALUE, + OP_DELETE_ATTR, + OP_END +} Operation_Type; + +typedef struct operation +{ + Operation_Type type; /* operation type */ + int csn; /* operation type */ + int value_index; /* value to add, remove or rename from */ + int old_dn_index; /* new dn - rename only */ +}Operation; + +typedef struct simulator_params +{ + int runs; /* number of runs */ + FILE *fout; /* output file */ + int value_count; /* number of values */ + int verbose; /* verbose mode */ + Operation *ops; /* operation sequence to apply */ + int op_count; +}Simulator_Params; + + +/* gloabl data */ +Simulator_Params sim; +char *g_values[] = +{ + "v", + "u", + "w", + NULL +}; + +/* forward declarations */ + +/* initialization */ +void process_cmd (int argc, char **argv); +void set_default_sim_params (); +int count_values (); +void parse_operations_file (char *name); +void parse_operation (char *line, int pos); +int value2index (char *value); +void print_usage (); + +/* simulation run */ +void run_simulation (); +void generate_operations (Operation **ops, int *op_count); +void generate_operation (Operation *op, int csn, int *last_dn_index); +int* generate_operation_order (int op_count, int seq_num); +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); +void init_entry_state (Entry_State *entry); +void init_value_state (Value_State *val, int seq_num); +void apply_operation (Entry_State *entry, Operation *op); +void free_operations (Operation **ops); +int ** new_perm_table (int op_count); +void free_perm_table (int ***perm_table, int op_count); +int get_perm_count (int op_count); +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table); +void apply_add_operation (Entry_State *entry, Operation *op); +void apply_value_delete_operation (Entry_State *entry, Operation *op); +void apply_attr_delete_operation (Entry_State *entry, Operation *op); +void apply_rename_operation (Entry_State *entry, Operation *op); +void make_value_distinguished (int op_csn, Entry_State *entry, int value_index); +void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index); +void purge_value_state (Value_State *value); +void purge_non_distinguished_csns (Value_State *value); +void resolve_value_state (Entry_State *entry, int value_index); +int value_distinguished_at_delete (Value_State *value, int attr_delete_csn); +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); + +/* data tracing */ +void dump_operations (Operation *ops, int op_count, int *order); +void dump_operation (Operation *op); +void dump_perm_table (int **perm_table, int op_count); +void dump_entry_state (Entry_State *entry); +void dump_value_state (Value_State *value); +void dump_list (int *list); + +/* misc functions */ +int max_val (int a, int b); +int is_list_empty (int *list); +int min_list_val (int *list); +int list_equal (int *list1, int *list2); + +int main (int argc, char **argv) +{ + int i; + + process_cmd (argc, argv); + + for (i = 0; i < sim.runs; i++) + { + fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); + run_simulation (); + fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); + } + + if (sim.fout != stdout) + fclose (sim.fout); + + return 0; +} + +void process_cmd (int argc, char **argv) +{ + int i; + + set_default_sim_params (); + + if (argc == 1) + { + return; + } + + if (strcmp (argv[1], "-h") == 0) /* print help */ + { + print_usage (); + exit (0); + } + + i = 1; + while (i < argc) + { + if (strcmp (argv[i], "-v") == 0) /* verbose mode */ + { + sim.verbose = 1; + i ++; + } + else if (strcmp (argv[i], "-n") == 0) + { + if (i < argc - 1) + { + int runs = atoi (argv[i + 1]); + if (runs > 0) + sim.runs = runs; + i+=2; + } + else + { + /* ONREPL print warning */ + i++; + } + } + else if (strcmp (argv[i], "-f") == 0) /* output file */ + { + if (i < argc - 1) + { + FILE *f = fopen (argv[i + 1], "w"); + if (f == 0) + { + printf ("failed to open output file; error - %s, using stdout\n", + strerror(errno)); + } + else + { + /* ONREPL print warning */ + sim.fout = f; + } + + i += 2; + } + else + i++; + } + else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ + { + if (i < argc - 1) + { + parse_operations_file (argv[i+1]); + i += 2; + } + else + { + /* ONREPL print warning */ + i ++; + } + } + else /* unknown option */ + { + printf ("unknown option - %s; ignored\n", argv[i]); + i ++; + } + + } +} + +void set_default_sim_params () +{ + memset (&sim, 0, sizeof (sim)); + sim.runs = 1; + sim.fout = stdout; + sim.value_count = count_values (); +} + +/* file format: <op count> + add <value> + delete <value> + delete attribute + rename <value> to <value> + */ +void parse_operations_file (char *name) +{ + FILE *file = fopen (name, "r"); + char line [256]; + int i; + + if (file == NULL) + { + printf ("failed to open operations file %s: error = %d\n", name, errno); + print_usage (); + exit (1); + } + + i = 0; + while (fgets (line, sizeof (line), file)) + { + if (i == 0) + { + /* read operation count */ + sim.op_count = atoi (line); + if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) + { + printf ("invalid operation count - %d; value must be between 1 and %d\n", + sim.op_count, MAX_OPS/2); + print_usage (); + exit (1); + } + else + { + sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); + } + } + else + { + if (strlen (line) == 0) /* skip empty lines */ + continue; + parse_operation (line, i); + } + + i ++; + } + +} + +void parse_operation (char *line, int i) +{ + sim.ops [i - 1].csn = i; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + if (strncmp (line, "add", 3) == 0) + { + sim.ops [i - 1].type = OP_ADD_VALUE; + sim.ops [i - 1].value_index = value2index (&line[4]); + } + else if (strncmp (line, "delete attribute", 16) == 0) + { + sim.ops [i - 1].type = OP_DELETE_ATTR; + } + else if (strncmp (line, "delete", 6) == 0) + { + sim.ops [i - 1].type = OP_DELETE_VALUE; + sim.ops [i - 1].value_index = value2index (&line[7]); + } + else if (strncmp (line, "rename", 6) == 0) + { + char *tok; + sim.ops [i - 1].type = OP_RENAME_ENTRY; + /* strtok() is not MT safe, but it is okay to call here because this is a program test */ + tok = strtok (&line[7], " "); + sim.ops [i - 1].old_dn_index = value2index (tok); + /* skip to */ + tok = strtok (NULL, " "); + tok = strtok (NULL, " "); + sim.ops [i - 1].value_index = value2index (tok); + } + else + { + /* invalid line */ + printf ("invalid operation: %s\n", line); + exit (1); + } +} + +int value2index (char *value) +{ + int i; + + for (i = 0; i < sim.value_count; i++) + { + if (strcmp (g_values[i], value) == 0) + return i; + } + + return -1; +} + +void print_usage () +{ + printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n" + "\t-h - print usage information\n" + "\t-n <number of simulations>; default - 1\n" + "\t-v - verbose mode\n" + "\t-f <output file> - by default, results are printed to the screen\n" + "\t-o <op file> - file that contains operation sequence to execute;\n" + "\tby default, random sequence is generated.\n"); +} + +int count_values () +{ + int i; + + for (i = 0; g_values[i]; i++); + + return i; +} + +void run_simulation () +{ + int *order; + int i; + int perm_count; + Entry_State entry_first, entry_current; + int error = 0; + + init_entry_state (&entry_first); + fprintf (sim.fout, "initial entry state :\n"); + dump_entry_state (&entry_first); + + if (sim.ops == NULL) + { + generate_operations (&sim.ops, &sim.op_count); + } + fprintf (sim.fout, "initial operation set:\n"); + dump_operations (sim.ops, sim.op_count, NULL/* order */); + + perm_count = get_perm_count (sim.op_count); + for (i = 0; i < perm_count; i++) + { + fprintf (sim.fout, "--------------------------------\n"); + fprintf (sim.fout, "simulation run %d\n", i + 1); + fprintf (sim.fout, "--------------------------------\n"); + order = generate_operation_order (sim.op_count, i); + if (i == 0) + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); + else + { + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); + error |= compare_entry_state (&entry_first, &entry_current, i + 1); + } + } + + switch (error) + { + case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); + break; + case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " + "the exact state does not match\n"); + break; + case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); + break; + } + + free_operations (&sim.ops); +} + +void generate_operations (Operation **ops, int *op_count) +{ + int i; + int last_dn_index = 0; + + /* generate number operations in the sequence */ + *op_count = slapi_rand () % (MAX_OPS / 2) + 1; + *ops = (Operation *)malloc (*op_count * sizeof (Operation)); + + for (i = 0; i < *op_count; i ++) + { + generate_operation (&((*ops)[i]), i + 1, &last_dn_index); + } +} + +void generate_operation (Operation *op, int csn, int *last_dn_index) +{ + /* generate operation type */ + op->type = slapi_rand () % OP_END; + + /* generate value to which operation applies */ + op->value_index = slapi_rand () % sim.value_count; + + op->csn = csn; + + /* generate new distinguished value */ + if (op->type == OP_RENAME_ENTRY) + { + op->old_dn_index = *last_dn_index; + while (op->value_index == op->old_dn_index) + op->value_index = slapi_rand () % sim.value_count; + *last_dn_index = op->value_index; + } +} + +int* generate_operation_order (int op_count, int seq_num) +{ + static int **perm_table = NULL; + + /* first request - generate pemutation table */ + if (seq_num == 0) + { + int elements [MAX_OPS]; + int i; + + if (perm_table) + free_perm_table (&perm_table, op_count); + perm_table = new_perm_table (op_count); + + for (i = 0; i < op_count; i++) + elements [i] = i; + + generate_perm_table (elements, op_count, 0 /* static part */, + perm_table); + /* dump_perm_table (perm_table, op_count);*/ + } + + return perm_table [seq_num]; +} + +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) +{ + int i; + + init_entry_state (entry); + + if (!sim.verbose) + { + if (!sim.verbose) + { + fprintf (sim.fout, "operation_sequence for this run:\n"); + dump_operations (ops, op_count, order); + } + } + + for (i = 0; i < op_count; i++) + { + apply_operation (entry, &(ops [order[i]])); + } + + if (!sim.verbose) + { + fprintf (sim.fout, "final entry state :\n"); + dump_entry_state (entry); + } + +} + +void init_entry_state (Entry_State *entry) +{ + int i; + + memset (entry, 0, sizeof (*entry)); + entry->attr_delete_csn = NOT_PRESENT; + + for (i = 0; i < sim.value_count; i++) + { + init_value_state (&(entry->values[i]), i); + } +} + +void init_value_state (Value_State *val, int seq_num) +{ + memset (val, 0, sizeof (*val)); + val->value_index = seq_num; + val->present = 1; + val->delete_csn = NOT_PRESENT; + if (seq_num > 0) /* only first value is distinguished */ + val->distinguished_csn = -1; +} + +void apply_operation (Entry_State *entry, Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: apply_add_operation (entry, op); + break; + + case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); + break; + + case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); + break; + + case OP_RENAME_ENTRY: apply_rename_operation (entry, op); + break; + } + + if (sim.verbose) + { + fprintf (sim.fout, "operation: "); + dump_operation (op); + fprintf (sim.fout, "\n"); + dump_entry_state (entry); + } +} + +void free_operations (Operation **ops) +{ + free (*ops); + *ops = NULL; +} + +int **new_perm_table (int op_count) +{ + int i; + int **perm_table; + int perm_count = get_perm_count (op_count); + + perm_table = (int**)malloc (perm_count * sizeof (int*)); + for (i = 0; i < perm_count; i ++) + perm_table [i] = (int*) malloc (op_count * sizeof (int)); + + return perm_table; +} + +void free_perm_table (int ***perm_table, int op_count) +{ + int i; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < perm_count; i ++) + free ((*perm_table)[i]); + + free (*perm_table); + *perm_table = NULL; +} + +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table) +{ + int i; + int elements_copy [MAX_OPS]; + int start_pos; + + if (element_count - 1 == static_part) + { + memcpy (*perm_table, elements, element_count * sizeof (int)); + return; + } + + start_pos = 0; + for (i = 0; i < element_count - static_part; i ++) + { + memcpy (elements_copy, elements, element_count * sizeof (int)); + elements_copy [static_part] = elements [static_part + i]; + elements_copy [static_part + i] = elements [static_part]; + generate_perm_table (elements_copy, element_count, static_part + 1, + &perm_table [start_pos]); + start_pos += get_perm_count (element_count - static_part - 1); + } +} + +int get_perm_count (int op_count) +{ + int i; + int perm_count = 1; + + for (i = 2; i <= op_count; i ++) + perm_count *= i; + + return perm_count; +} + +void apply_add_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].presense_csn < op->csn) + { + entry->values[op->value_index].presense_csn = op->csn; + entry->values[op->value_index].present = 1; + resolve_value_state (entry, op->value_index); + } +} + +void apply_value_delete_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].delete_csn < op->csn) + { + entry->values[op->value_index].delete_csn = op->csn; + resolve_value_state (entry, op->value_index); + } +} + +void apply_attr_delete_operation (Entry_State *entry, Operation *op) +{ + int i; + + if (entry->attr_delete_csn < op->csn) + { + entry->attr_delete_csn = op->csn; + + for (i = 0; i < sim.value_count; i++) + { + resolve_value_state (entry, i); + } + } +} + +void apply_rename_operation (Entry_State *entry, Operation *op) +{ + if (entry->dn_csn < op->csn) + { + entry->dn_index = op->value_index; + entry->dn_csn = op->csn; + } + + make_value_non_distinguished (op->csn, entry, op->old_dn_index); + make_value_distinguished (op->csn, entry, op->value_index); +} + +void make_value_distinguished (int op_csn, Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + + if (value->distinguished_csn < op_csn) + { + value->distinguished_csn = op_csn; + + if (value->presense_csn < op_csn) + { + value->present = 1; + value->presense_csn = op_csn; + } + + resolve_value_state (entry, value_index); + } +} + +void make_value_non_distinguished (int op_csn, Entry_State *entry, int value_index) +{ + int i = 0; + int index; + Value_State *value = &(entry->values[value_index]); + + if (op_csn < value->distinguished_csn) + return; + + /* insert into sorted list */ + while (value->non_distinguished_csns[i] && value->non_distinguished_csns[i] < op_csn) + i++; + + if (value->non_distinguished_csns[i] == 0) + value->non_distinguished_csns[i] = op_csn; + else + { + index = i; + + while (value->non_distinguished_csns[i]) + i++; + + memcpy (&(value->non_distinguished_csns[index + 1]), + &(value->non_distinguished_csns[index]), (i - index) * sizeof (int)); + + value->non_distinguished_csns[index] = op_csn; + } + + resolve_value_state (entry, value_index); +} + +void purge_value_state (Value_State *value) +{ + /* value state information can be purged once a value was + readed/made distinguished because at that point we know that the value + existed/was distinguished */ + + purge_non_distinguished_csns (value); + + if (value->delete_csn < max_val (value->distinguished_csn, value->presense_csn)) + value->delete_csn = NOT_PRESENT; +} + +void purge_non_distinguished_csns (Value_State *value) +{ + int i = 0; + int index; + + while (value->non_distinguished_csns[i] && + value->non_distinguished_csns[i] < value->distinguished_csn) + i ++; + + if (i > 0) + { + index = i-1; + while (value->non_distinguished_csns[i]) + i ++; + + if (i > index + 1) + { + memcpy (value->non_distinguished_csns, &value->non_distinguished_csns[index+1], + (i - index - 1) * sizeof (int)); + memset (&(value->non_distinguished_csns[index+1]), 0, sizeof (int) * (i - index - 1)); + } + else + { + memset (value->non_distinguished_csns, 0, sizeof (int) * i); + } + } +} + +int is_list_empty (int *list) +{ + return (list[0] == 0); +} + +int min_list_val (int *list) +{ + return (list [0]); +} + +void resolve_value_state (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + + purge_value_state (value); + + /* no deletes that effect the state */ + if (max_val (value->delete_csn, entry->attr_delete_csn) < + max_val (value->distinguished_csn, value->presense_csn)) + return; + + if (value->present) /* check if it should be removed based on the current state */ + { + if (!value_distinguished_at_delete (value, entry->attr_delete_csn)) + { + /* note that we keep presence csn because we might have to restore + the value in the future */ + value->present = 0; + } + } + else /* not present - check if it should be restored */ + { + if (value_distinguished_at_delete (value, entry->attr_delete_csn)) + { + value->present = 1; + } + } +} + +/* Note we can't trim distinguished_csn (even during regular trimming) + because in some cases we would not be able to figure out whether + a value was distinguished or not at the time of deletion. + + Example 1: Example2: + csn order operation csn order operation + 1 1 make V distinguished 1 1 make V distinguished + 3 3 delete V 2 2 make V non distinguished + 4 2 make V non-distinguished 3 4 delete V + 4 3 make V non distinguished (on another server) + + if the csns up to 2 were triimed, when delete operation is received, the state + is exactly the same in both examples but in example one delete should not go + through while in example 2 it should + + */ +int value_distinguished_at_delete (Value_State *value, int attr_delete_csn) +{ + if (value->distinguished_csn >= 0 && + (is_list_empty (value->non_distinguished_csns) || + min_list_val (value->non_distinguished_csns) > + max_val (value->delete_csn, attr_delete_csn))) + return 1; + else + return 0; +} + +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) +{ + int j; + int error = 0; + + /* first - quick check for present / not present */ + for (j = 0; j < sim.value_count; j++) + { + if (entry1->values[j].present != entry2->values[j].present) + { + fprintf (sim.fout, + "value %s is %s present in the first run but %s present in the %d run\n", + g_values[j], entry1->values[j].present ? "" : "not", + entry2->values[j].present ? "" : "not", run); + error = 1; + } + } + + if (error) + return 3; + + /* compare value state */ + error = 0; + if (entry1->attr_delete_csn != entry2->attr_delete_csn) + { + fprintf (sim.fout, "attribute delete csn is %d for run 1 " + "but is %d for run %d\n", entry1->attr_delete_csn, + entry2->attr_delete_csn, run); + error = 1; + } + + for (j = 0; j < sim.value_count; j++) + { + if (entry1->values[j].presense_csn != entry2->values[j].presense_csn) + { + fprintf (sim.fout, "presence csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[j], entry1->values[j].presense_csn, + entry2->values[j].presense_csn, run); + error = 1; + } + + if (entry1->values[j].distinguished_csn != entry2->values[j].distinguished_csn) + { + fprintf (sim.fout, "distinguished csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[j], entry1->values[j].distinguished_csn, + entry2->values[j].distinguished_csn, run); + error = 1; + } + + if (entry1->values[j].delete_csn != entry2->values[j].delete_csn) + { + fprintf (sim.fout, "delete csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[j], entry1->values[j].delete_csn, + entry2->values[j].delete_csn, run); + error = 1; + } + + if (!list_equal (entry1->values[j].non_distinguished_csns, + entry2->values[j].non_distinguished_csns)) + { + fprintf (sim.fout, "pending list mismatch for valye %s in runs 1 and %d\n", + g_values[j], run); + dump_list (entry1->values[j].non_distinguished_csns); + dump_list (entry2->values[j].non_distinguished_csns); + } + } + + if (error != 0) + { + return 1; + } + else + return 0; +} + +void dump_operations (Operation *ops, int op_count, int *order) +{ + int index; + int i; + + for (i = 0; i < op_count; i ++) + { + if (order == NULL) /* current order */ + index = i; + else + index = order [i]; + + dump_operation (&ops[index]); + } + + fprintf (sim.fout, "\n"); +} + +void dump_operation (Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: + fprintf (sim.fout, "\t%d add value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_VALUE: + fprintf (sim.fout, "\t%d delete value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_ATTR: + fprintf (sim.fout, "\t%d delete attribute\n", op->csn); + break; + case OP_RENAME_ENTRY: + fprintf (sim.fout, "\t%d rename entry from %s to %s\n", op->csn, + g_values [op->old_dn_index], g_values [op->value_index]); + break; + } +} + +void dump_perm_table (int **perm_table, int op_count) +{ + int i, j; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < op_count; i++) + { + for (j = 0; j < perm_count; j++) + { + fprintf (sim.fout, "%d ", perm_table [j][i]); + } + + fprintf (sim.fout, "\n"); + } +} + +void dump_entry_state (Entry_State *entry) +{ + int i; + fprintf (sim.fout, "\tentry dn: %s; dn csn - %d\n", + g_values [entry->dn_index], entry->dn_csn); + + if (sim.verbose) + fprintf (sim.fout, "\n"); + + for (i = 0; i < sim.value_count; i++) + { + dump_value_state (&(entry->values[i])); + } + + fprintf (sim.fout, "\n"); +} + +void dump_value_state (Value_State *value) +{ + fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index], + value->present ? "present" : "not present"); + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presense_csn); + fprintf (sim.fout, "\t\tdistinguished csn: %d\n", value->distinguished_csn); + fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn); + fprintf (sim.fout, "\t\tnon distinguished csns: "); + + dump_list (value->non_distinguished_csns); + + fprintf (sim.fout, "\n"); + } +} + +void dump_list (int *list) +{ + int i = 0; + + while (list[i]) + { + fprintf (sim.fout, "%d ", list[i]); + i ++; + } + + fprintf (sim.fout, "\n"); +} + +/* misc functions */ +int max_val (int a, int b) +{ + if (a >= b) + return a; + else + return b; +} + +int list_equal (int *list1, int *list2) +{ + int i; + + i = 0; + while (list1[i] && list2[i]) + { + if (list1[i] != list2[i]) + return 0; + + i ++; + } + + if (list1[i] != list2[i]) + return 0; + else + return 1; +} diff --git a/ldap/servers/plugins/replication/tests/dnp_sim2.c b/ldap/servers/plugins/replication/tests/dnp_sim2.c new file mode 100644 index 00000000..e1838aa6 --- /dev/null +++ b/ldap/servers/plugins/replication/tests/dnp_sim2.c @@ -0,0 +1,972 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dnp_simulation.c - this file varifies the correctness of dnp algorithm + by generating random sequences of operations, applying + the algorithm and outputing the result + + usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>] + -h - print usage information. + -n <number of simulations> - how many simulations to perform; default - 1. + -v - verbose mode (prints full entry state after each operation execution) + -f <output file> - file where results are stored; by default results are + printed to the screen. + -o <op file> - file that contains operation sequence to execute; by default, + random sequence is generated. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <memory.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#define MAX_OPS 18 /* maximum number of operations in a simulation */ +#define MAX_VALS 10 /* maximum number of values is entry or dn */ +#define NOT_PRESENT -1 + +/* data types */ +typedef struct value_state +{ + int value_index; /* value */ + int presence_csn; /* last time at which we know the value was present */ + int distinguished_index; /* index into dncsn list */ + int delete_csn; /* last attempt to delete this value */ + int present; /* flag that tells whether the value iscurrently present */ +} Value_State; + +typedef struct dn_csn +{ + int csn; /* dn csn */ + int value_index; /* dn value */ +} Dn_Csn; + +typedef struct entry_state +{ + Dn_Csn dn_csns [MAX_VALS + 1]; /* list of dn csns for this entry */ + int dn_csn_count; /* csn of the current dn */ + Value_State values[MAX_VALS]; /* values of the attribute */ + int attr_delete_csn; /* last attempt to delete the entire attribute */ +} Entry_State; + +typedef enum +{ + OP_ADD_VALUE, + OP_DELETE_VALUE, + OP_RENAME_ENTRY, + OP_DELETE_ATTR, + OP_END +} Operation_Type; + +typedef struct operation +{ + Operation_Type type; /* operation type */ + int csn; /* operation type */ + int value_index; /* value to add, remove or rename from */ +}Operation; + +typedef struct simulator_params +{ + int runs; /* number of runs */ + FILE *fout; /* output file */ + int value_count; /* number of values */ + int verbose; /* verbose mode */ + Operation *ops; /* operation sequence to apply */ + int op_count; +}Simulator_Params; + + +/* gloabl data */ +Simulator_Params sim; +char *g_values[] = +{ + "v", + "u", + "w", + NULL +}; + +/* forward declarations */ + +/* initialization */ +void process_cmd (int argc, char **argv); +void set_default_sim_params (); +int count_values (); +void parse_operations_file (char *name); +void parse_operation (char *line, int pos); +int value2index (char *value); +void print_usage (); + +/* simulation run */ +void run_simulation (); +void generate_operations (Operation **ops, int *op_count); +void generate_operation (Operation *op, int csn); +int* generate_operation_order (int op_count, int seq_num); +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); +void init_entry_state (Entry_State *entry); +void init_value_state (Value_State *val, int seq_num); +void apply_operation (Entry_State *entry, Operation *op); +void free_operations (Operation **ops); +int ** new_perm_table (int op_count); +void free_perm_table (int ***perm_table, int op_count); +int get_perm_count (int op_count); +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table); +void apply_add_operation (Entry_State *entry, Operation *op); +void apply_value_delete_operation (Entry_State *entry, Operation *op); +void apply_attr_delete_operation (Entry_State *entry, Operation *op); +void apply_rename_operation (Entry_State *entry, Operation *op); +void purge_value_state (Entry_State *entry, int index); +void resolve_value_state (Entry_State *entry, int value_index); +int value_distinguished_at_delete (Entry_State *entry, int value_index); +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); + +/* dnc_csn handling */ +int dn_csn_add (Entry_State *entry, int value_index, int csn); +int get_value_dn_csn (Entry_State *entry, int value_index); + +/* data tracing */ +void dump_operations (Operation *ops, int op_count, int *order); +void dump_operation (Operation *op); +void dump_perm_table (int **perm_table, int op_count); +void dump_entry_state (Entry_State *entry); +void dump_value_state (Value_State *value); +void dump_dn_csn_list (Entry_State *entry); + +/* misc functions */ +int max_val (int a, int b); + +int main (int argc, char **argv) +{ + int i; + + process_cmd (argc, argv); + + for (i = 0; i < sim.runs; i++) + { + fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); + run_simulation (); + fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); + } + + if (sim.fout != stdout) + fclose (sim.fout); + + return 0; +} + +void process_cmd (int argc, char **argv) +{ + int i; + + set_default_sim_params (); + + if (argc == 1) + { + return; + } + + if (strcmp (argv[1], "-h") == 0) /* print help */ + { + print_usage (); + exit (0); + } + + i = 1; + while (i < argc) + { + if (strcmp (argv[i], "-v") == 0) /* verbose mode */ + { + sim.verbose = 1; + i ++; + } + else if (strcmp (argv[i], "-n") == 0) + { + if (i < argc - 1) + { + int runs = atoi (argv[i + 1]); + if (runs > 0) + sim.runs = runs; + i+=2; + } + else + { + /* ONREPL print warning */ + i++; + } + } + else if (strcmp (argv[i], "-f") == 0) /* output file */ + { + if (i < argc - 1) + { + FILE *f = fopen (argv[i + 1], "w"); + if (f == 0) + { + printf ("failed to open output file; error - %s, using stdout\n", + strerror(errno)); + } + else + { + /* ONREPL print warning */ + sim.fout = f; + } + + i += 2; + } + else + i++; + } + else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ + { + if (i < argc - 1) + { + parse_operations_file (argv[i+1]); + i += 2; + } + else + { + /* ONREPL print warning */ + i ++; + } + } + else /* unknown option */ + { + printf ("unknown option - %s; ignored\n", argv[i]); + i ++; + } + + } +} + +void set_default_sim_params () +{ + memset (&sim, 0, sizeof (sim)); + sim.runs = 1; + sim.fout = stdout; + sim.value_count = count_values (); +} + +/* file format: <op count> + add <value> + delete <value> + delete attribute + rename to <value> + + all spaces are significant + */ +void parse_operations_file (char *name) +{ + FILE *file = fopen (name, "r"); + char line [256]; + int i; + + if (file == NULL) + { + printf ("failed to open operations file %s: error = %d\n", name, errno); + print_usage (); + exit (1); + } + + i = 0; + while (fgets (line, sizeof (line), file)) + { + if (i == 0) + { + /* read operation count */ + sim.op_count = atoi (line); + if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) + { + printf ("invalid operation count - %d; value must be between 1 and %d\n", + sim.op_count, MAX_OPS/2); + print_usage (); + exit (1); + } + else + { + sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); + } + } + else + { + if (strlen (line) == 0) /* skip empty lines */ + continue; + parse_operation (line, i); + } + + i ++; + } +} + +void parse_operation (char *line, int i) +{ + sim.ops [i - 1].csn = i; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + if (strncmp (line, "add", 3) == 0) + { + sim.ops [i - 1].type = OP_ADD_VALUE; + sim.ops [i - 1].value_index = value2index (&line[4]); + } + else if (strncmp (line, "delete attribute", 16) == 0) + { + sim.ops [i - 1].type = OP_DELETE_ATTR; + } + else if (strncmp (line, "delete", 6) == 0) + { + sim.ops [i - 1].type = OP_DELETE_VALUE; + sim.ops [i - 1].value_index = value2index (&line[7]); + } + else if (strncmp (line, "rename to", 6) == 0) + { + sim.ops [i - 1].type = OP_RENAME_ENTRY; + sim.ops [i - 1].value_index = value2index (&line[10]); + } + else + { + /* invalid line */ + printf ("invalid operation: %s\n", line); + exit (1); + } +} + +int value2index (char *value) +{ + int i; + + for (i = 0; i < sim.value_count; i++) + { + if (strcmp (g_values[i], value) == 0) + return i; + } + + return -1; +} + +void print_usage () +{ + printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n" + "\t-h - print usage information\n" + "\t-n <number of simulations>; default - 1\n" + "\t-v - verbose mode\n" + "\t-f <output file> - by default, results are printed to the screen\n" + "\t-o <op file> - file that contains operation sequence to execute;\n" + "\tby default, random sequence is generated.\n"); +} + +int count_values () +{ + int i; + + for (i = 0; g_values[i]; i++); + + return i; +} + +void run_simulation () +{ + int *order; + int i; + int perm_count; + Entry_State entry_first, entry_current; + int error = 0; + + init_entry_state (&entry_first); + fprintf (sim.fout, "initial entry state :\n"); + dump_entry_state (&entry_first); + + if (sim.ops == NULL) + { + generate_operations (&sim.ops, &sim.op_count); + } + fprintf (sim.fout, "initial operation set:\n"); + dump_operations (sim.ops, sim.op_count, NULL/* order */); + + //DebugBreak (); + + perm_count = get_perm_count (sim.op_count); + for (i = 0; i < perm_count; i++) + { + fprintf (sim.fout, "--------------------------------\n"); + fprintf (sim.fout, "simulation run %d\n", i + 1); + fprintf (sim.fout, "--------------------------------\n"); + order = generate_operation_order (sim.op_count, i); + if (i == 0) + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); + else + { + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); + error |= compare_entry_state (&entry_first, &entry_current, i + 1); + } + } + + switch (error) + { + case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); + break; + case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " + "the exact state does not match\n"); + break; + case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); + break; + } + + free_operations (&sim.ops); +} + +void generate_operations (Operation **ops, int *op_count) +{ + int i; + + /* generate number operations in the sequence */ + *op_count = slapi_rand () % (MAX_OPS / 2) + 1; + *ops = (Operation *)malloc (*op_count * sizeof (Operation)); + + for (i = 0; i < *op_count; i ++) + { + generate_operation (&((*ops)[i]), i + 1); + } +} + +void generate_operation (Operation *op, int csn) +{ + /* generate operation type */ + op->type = slapi_rand () % OP_END; + + /* generate value to which operation applies */ + op->value_index = slapi_rand () % sim.value_count; + + op->csn = csn; +} + +int* generate_operation_order (int op_count, int seq_num) +{ + static int **perm_table = NULL; + + /* first request - generate pemutation table */ + if (seq_num == 0) + { + int elements [MAX_OPS]; + int i; + + if (perm_table) + free_perm_table (&perm_table, op_count); + perm_table = new_perm_table (op_count); + + for (i = 0; i < op_count; i++) + elements [i] = i; + + generate_perm_table (elements, op_count, 0 /* static part */, + perm_table); + } + + return perm_table [seq_num]; +} + +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) +{ + int i; + + init_entry_state (entry); + + if (!sim.verbose) + { + if (!sim.verbose) + { + fprintf (sim.fout, "operation_sequence for this run:\n"); + dump_operations (ops, op_count, order); + } + } + + for (i = 0; i < op_count; i++) + { + apply_operation (entry, &(ops [order[i]])); + } + + if (!sim.verbose) + { + fprintf (sim.fout, "final entry state :\n"); + dump_entry_state (entry); + } + +} + +void init_entry_state (Entry_State *entry) +{ + int i; + + memset (entry, 0, sizeof (*entry)); + entry->attr_delete_csn = NOT_PRESENT; + entry->dn_csn_count = 1; + + for (i = 0; i < sim.value_count; i++) + { + init_value_state (&(entry->values[i]), i); + } +} + +void init_value_state (Value_State *val, int seq_num) +{ + memset (val, 0, sizeof (*val)); + val->value_index = seq_num; + val->present = 1; + val->delete_csn = NOT_PRESENT; + if (seq_num > 0) /* only first value is distinguished */ + val->distinguished_index = -1; +} + +void apply_operation (Entry_State *entry, Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: apply_add_operation (entry, op); + break; + + case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); + break; + + case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); + break; + + case OP_RENAME_ENTRY: apply_rename_operation (entry, op); + break; + } + + if (sim.verbose) + { + fprintf (sim.fout, "operation: "); + dump_operation (op); + fprintf (sim.fout, "\n"); + dump_entry_state (entry); + } +} + +void free_operations (Operation **ops) +{ + free (*ops); + *ops = NULL; +} + +int **new_perm_table (int op_count) +{ + int i; + int **perm_table; + int perm_count = get_perm_count (op_count); + + perm_table = (int**)malloc (perm_count * sizeof (int*)); + for (i = 0; i < perm_count; i ++) + perm_table [i] = (int*) malloc (op_count * sizeof (int)); + + return perm_table; +} + +void free_perm_table (int ***perm_table, int op_count) +{ + int i; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < perm_count; i ++) + free ((*perm_table)[i]); + + free (*perm_table); + *perm_table = NULL; +} + +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table) +{ + int i; + int elements_copy [MAX_OPS]; + int start_pos; + + if (element_count - 1 == static_part) + { + memcpy (*perm_table, elements, element_count * sizeof (int)); + return; + } + + start_pos = 0; + for (i = 0; i < element_count - static_part; i ++) + { + memcpy (elements_copy, elements, element_count * sizeof (int)); + elements_copy [static_part] = elements [static_part + i]; + elements_copy [static_part + i] = elements [static_part]; + generate_perm_table (elements_copy, element_count, static_part + 1, + &perm_table [start_pos]); + start_pos += get_perm_count (element_count - static_part - 1); + } +} + +int get_perm_count (int op_count) +{ + int i; + int perm_count = 1; + + for (i = 2; i <= op_count; i ++) + perm_count *= i; + + return perm_count; +} + +void apply_add_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].presence_csn < op->csn) + { + entry->values[op->value_index].presence_csn = op->csn; + resolve_value_state (entry, op->value_index); + } +} + +void apply_value_delete_operation (Entry_State *entry, Operation *op) +{ + if (entry->values[op->value_index].delete_csn < op->csn) + { + entry->values[op->value_index].delete_csn = op->csn; + resolve_value_state (entry, op->value_index); + } +} + +void apply_attr_delete_operation (Entry_State *entry, Operation *op) +{ + int i; + + if (entry->attr_delete_csn < op->csn) + { + entry->attr_delete_csn = op->csn; + + for (i = 0; i < sim.value_count; i++) + { + resolve_value_state (entry, i); + } + } +} + +void apply_rename_operation (Entry_State *entry, Operation *op) +{ + int index; + + if (entry->values[op->value_index].presence_csn == NOT_PRESENT) + entry->values[op->value_index].presence_csn = op->csn; + + index = dn_csn_add (entry, op->value_index, op->csn); + + if (index > 0) + resolve_value_state (entry, entry->dn_csns[index - 1].value_index); + + resolve_value_state (entry, entry->dn_csns[index].value_index); +} + +void purge_value_state (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + int value_distinguished_csn = get_value_dn_csn (entry, value_index); + + if (value_distinguished_csn == -1 && value->presence_csn > value->delete_csn) + value->delete_csn = NOT_PRESENT; + else if (value->delete_csn < max_val (value_distinguished_csn, value->presence_csn)) + value->delete_csn = NOT_PRESENT; +} + +void resolve_value_state (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + int value_distinguished_csn = get_value_dn_csn (entry, value_index); + + purge_value_state (entry, value_index); + + /* no deletes that effect the state */ + if (max_val (value->delete_csn, entry->attr_delete_csn) < + max_val (value_distinguished_csn, value->presence_csn)) + { + value->present = 1; + return; + } + + if (value->present) /* check if it should be removed based on the current state */ + { + if (!value_distinguished_at_delete (entry, value_index)) + { + value->present = 0; + } + } + else /* not present - check if it should be restored */ + { + if (value_distinguished_at_delete (entry, value_index)) + { + value->present = 1; + } + } +} + +int value_distinguished_at_delete (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values[value_index]); + int value_distinguished_csn = get_value_dn_csn (entry, value_index); + int delete_csn; + int i; + + /* value has never been distinguished */ + if (value_distinguished_csn == -1) + return 0; + + delete_csn = max_val (entry->attr_delete_csn, value->delete_csn); + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > delete_csn) + break; + } + + /* i is never equal to 0 because the csn of the first element is always + smaller than csn of any operation we can receive */ + return (entry->dn_csns[i-1].value_index == value_index); +} + +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) +{ + int i; + int error = 0; + + /* first - quick check for present / not present */ + for (i = 0; i < sim.value_count; i++) + { + if (entry1->values[i].present != entry2->values[i].present) + { + fprintf (sim.fout, + "value %s is %s present in the first run but %s present in the %d run\n", + g_values[i], entry1->values[i].present ? "" : "not", + entry2->values[i].present ? "" : "not", run); + error = 1; + } + } + + if (error) + return 3; + + /* compare dnc_csn list */ + if (entry1->dn_csn_count != entry2->dn_csn_count) + { + fprintf (sim.fout, "dn_csn count is %d for run 1 and %d for run %d\n", + entry1->dn_csn_count, entry2->dn_csn_count, run); + error = 1; + } + + for (i = 0; i < entry1->dn_csn_count; i++) + { + if (entry1->dn_csns [i].csn != entry2->dn_csns [i].csn || + entry1->dn_csns [i].value_index != entry2->dn_csns [i].value_index) + { + fprintf (sim.fout,"elements %d of dn csn list are different:\n" + "\tfirst run: csn - %d, value - %s\n" + "\t%d run: csn - %d, value - %s\n", i, + entry1->dn_csns [i].csn, + g_values[entry1->dn_csns [i].value_index], + run, entry2->dn_csns [i].csn, + g_values[entry2->dn_csns [i].value_index]); + + error = 1; + } + } + + /* compare value state */ + if (entry1->attr_delete_csn != entry2->attr_delete_csn) + { + fprintf (sim.fout, "attribute delete csn is %d for run 1 " + "but is %d for run %d\n", entry1->attr_delete_csn, + entry2->attr_delete_csn, run); + error = 1; + } + + for (i = 0; i < sim.value_count; i++) + { + if (entry1->values[i].presence_csn != entry2->values[i].presence_csn) + { + fprintf (sim.fout, "presence csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[i], entry1->values[i].presence_csn, + entry2->values[i].presence_csn, run); + error = 1; + } + + if (entry1->values[i].distinguished_index != entry2->values[i].distinguished_index) + { + fprintf (sim.fout, "distinguished index for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[i], + entry1->values[i].distinguished_index, + entry2->values[i].distinguished_index, run); + error = 1; + } + + if (entry1->values[i].delete_csn != entry2->values[i].delete_csn) + { + fprintf (sim.fout, "delete csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[i], entry1->values[i].delete_csn, + entry2->values[i].delete_csn, run); + error = 1; + } + } + + if (error != 0) + { + return 1; + } + else + return 0; +} + +int dn_csn_add (Entry_State *entry, int value_index, int csn) +{ + int i, j; + int distinguished_index; + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > csn) + break; + } + + if (i < entry->dn_csn_count) + { + distinguished_index = i; + for (j = i; j < entry->dn_csn_count; j ++) + { + if (entry->dn_csns[j].value_index == value_index) + distinguished_index = j + 1; + + if (entry->values [entry->dn_csns[j].value_index].distinguished_index == j) + entry->values [entry->dn_csns[j].value_index].distinguished_index ++; + } + + memcpy (&(entry->dn_csns[i+1]), &(entry->dn_csns[i]), + (entry->dn_csn_count - i) * sizeof (Dn_Csn)); + } + else + { + distinguished_index = entry->dn_csn_count; + } + + entry->values[value_index].distinguished_index = distinguished_index; + entry->dn_csns[i].csn = csn; + entry->dn_csns[i].value_index = value_index; + entry->dn_csn_count ++; + + return i; +} + +int get_value_dn_csn (Entry_State *entry, int value_index) +{ + Value_State *value = &(entry->values [value_index]); + + if (value->distinguished_index == -1) + return -1; + else + return entry->dn_csns [value->distinguished_index].csn; +} + +void dump_operations (Operation *ops, int op_count, int *order) +{ + int index; + int i; + + for (i = 0; i < op_count; i ++) + { + if (order == NULL) /* current order */ + index = i; + else + index = order [i]; + + dump_operation (&ops[index]); + } + + fprintf (sim.fout, "\n"); +} + +void dump_operation (Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: + fprintf (sim.fout, "\t%d add value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_VALUE: + fprintf (sim.fout, "\t%d delete value %s\n", op->csn, + g_values [op->value_index]); + break; + case OP_DELETE_ATTR: + fprintf (sim.fout, "\t%d delete attribute\n", op->csn); + break; + case OP_RENAME_ENTRY: + fprintf (sim.fout, "\t%d rename entry to %s\n", op->csn, + g_values [op->value_index]); + break; + } +} + +void dump_perm_table (int **perm_table, int op_count) +{ + int i, j; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < op_count; i++) + { + for (j = 0; j < perm_count; j++) + { + fprintf (sim.fout, "%d ", perm_table [j][i]); + } + + fprintf (sim.fout, "\n"); + } +} + +void dump_entry_state (Entry_State *entry) +{ + int i; + + dump_dn_csn_list (entry); + + for (i = 0; i < sim.value_count; i++) + { + dump_value_state (&(entry->values[i])); + } + + fprintf (sim.fout, "\n"); +} + +void dump_value_state (Value_State *value) +{ + fprintf (sim.fout, "\tvalue %s is %s\n", g_values[value->value_index], + value->present ? "present" : "not present"); + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tpresent csn: %d\n", value->presence_csn); + fprintf (sim.fout, "\t\tdistinguished index: %d\n", value->distinguished_index); + fprintf (sim.fout, "\t\tdelete value csn: %d\n", value->delete_csn); + } +} + +void dump_dn_csn_list (Entry_State *entry) +{ + int i; + + fprintf (sim.fout, "\tdn csn list: \n"); + for (i = 0; i < entry->dn_csn_count; i++) + { + fprintf (sim.fout, "\t\tvalue: %s, csn: %d\n", + g_values[entry->dn_csns[i].value_index], entry->dn_csns[i].csn); + } +} + +/* misc functions */ +int max_val (int a, int b) +{ + if (a >= b) + return a; + else + return b; +} diff --git a/ldap/servers/plugins/replication/tests/dnp_sim3.c b/ldap/servers/plugins/replication/tests/dnp_sim3.c new file mode 100644 index 00000000..d018597a --- /dev/null +++ b/ldap/servers/plugins/replication/tests/dnp_sim3.c @@ -0,0 +1,1489 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* dnp_simulation.c - this file varifies the correctness of dnp algorithm + by generating random sequences of operations, applying + the algorithm and outputing the result + + usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>] + -h - print usage information. + -n <number of simulations> - how many simulations to perform; default - 1. + -v - verbose mode (prints full entry state after each operation execution) + -f <output file> - file where results are stored; by default results are + printed to the screen. + -o <op file> - file that contains operation sequence to execute; by default, + random sequence is generated. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <memory.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#define MAX_OPS 18 /* maximum number of operations in a simulation */ +#define MAX_VALS 10 /* maximum number of values is entry or dn */ +#define MAX_ATTR_NAME 16 /* max length of the attribute name */ +#define NOT_PRESENT -1 +#define SV_ATTR_NAME "sv_attr" /* name of the singlevalued attribute */ +#define MV_ATTR_NAME "mv_attr" /* name of the multivalued attribute */ + +/* data types */ + +/* value */ +typedef struct value_state +{ + int value_index; /* value */ + int presence_csn; /* last time at which we know the value was present */ + int delete_csn; /* last attempt to delete this value */ + int present; /* flag that tells whether the value is present */ +} Value_State; + +/* shared attribute state */ +typedef struct attr_state +{ + int delete_csn; /* last deletion csn */ + int present; /* flag that tells whether the attribute is present */ +}Attr_State; + +/* singlevalued attribute */ +typedef struct sv_attr_state +{ + Attr_State attr_state; /* shared attribute state */ + Value_State current_value; /* current attribute value */ + Value_State *pending_value; /* latest pending value */ +} SV_Attr_State; + +/* maltivalued attribute */ +typedef struct mv_attr_state +{ + Attr_State attr_state; /* shared attribute state */ + Value_State values [MAX_VALS]; /* latest pending value */ + int value_count; /* number of values in the array */ +} MV_Attr_State; + +/* node of dn_csn_list */ +typedef struct dn_csn +{ + int csn; /* dn csn */ + int sv_attr; /* is this single valued or multivalued attr */ + int value_index; /* dn value */ +} Dn_Csn; + +typedef struct entry_state +{ + Dn_Csn dn_csns [MAX_VALS + 1]; /* list of dn csns for this entry */ + int dn_csn_count; /* csn of the current dn */ + SV_Attr_State sv_attr; /* singlevalued attribute */ + MV_Attr_State mv_attr; /* singlevalued attribute */ +} Entry_State; + +typedef enum +{ + OP_ADD_VALUE, + OP_DELETE_VALUE, + OP_RENAME_ENTRY, + OP_DELETE_ATTR, + OP_END +} Operation_Type; + +typedef struct operation +{ + Operation_Type type; /* operation type */ + int csn; /* operation csn */ + int sv_attr; /* is this applied to singlevalued attribute */ + int value_index; /* value to add, remove or rename from */ + int delete_old_rdn; /* rename only */ + int old_rdn_sv_attr; /* is oldrdn a singlevalued attribute */ + int old_rdn_value_index; /* index of old_rdn */ +}Operation; + +typedef struct simulator_params +{ + int runs; /* number of runs */ + FILE *fout; /* output file */ + int value_count; /* number of values */ + int verbose; /* verbose mode */ + Operation *ops; /* operation sequence to apply */ + int op_count; +}Simulator_Params; + + +/* gloabl data */ +Simulator_Params sim; +char *g_values[] = +{ + "v", + "u", + "w", + NULL +}; + +/* forward declarations */ + +/* initialization */ +void process_cmd (int argc, char **argv); +void set_default_sim_params (); +int count_values (); +void parse_operations_file (char *name); +void parse_operation (char *line, int pos); +int value2index (char *value); +void print_usage (); + +/* simulation run */ +void run_simulation (); +void generate_operations (Operation **ops, int *op_count); +void generate_operation (Operation *op, int csn); +int* generate_operation_order (int op_count, int seq_num); +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry); +void init_entry_state (Entry_State *entry); +void init_sv_attr_state (SV_Attr_State *sv_attr); +void init_mv_attr_state (MV_Attr_State *mv_attr); +void init_value_state (Value_State *val, int seq_num); +void free_operations (Operation **ops); +int ** new_perm_table (int op_count); +void free_perm_table (int ***perm_table, int op_count); +int get_perm_count (int op_count); +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table); +void apply_operation (Entry_State *entry, Operation *op); +void apply_add_operation (Entry_State *entry, Operation *op); +void apply_value_delete_operation (Entry_State *entry, Operation *op); +void apply_attr_delete_operation (Entry_State *entry, Operation *op); +void apply_rename_operation (Entry_State *entry, Operation *op); +void resolve_mv_attr_state (Entry_State *entry, Value_State *value); +void resolve_sv_attr_state (Entry_State *entry, Value_State *value); +void purge_sv_attr_state (Entry_State *entry); +void purge_mv_attr_state (Entry_State *entry, Value_State *value); +int value_distinguished_at_csn (Entry_State *entry, int sv_attr, Value_State *value, int csn); + +/* state comparison */ +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run); +int compare_entry_state_quick (Entry_State *entry1, Entry_State *entry2, int run); +int compare_sv_attr_state_quick (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run); +int compare_mv_attr_state_quick (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run); +int compare_sv_attr_state (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run); +int compare_mv_attr_state (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run); +int compare_value_state (Value_State *value1, Value_State *value2, int run); + +/* dnc_csn handling */ +int dn_csn_add (Entry_State *entry, int sv_attr, int value_index, int csn); + +/* data tracing */ +void dump_operations (Operation *ops, int op_count, int *order); +void dump_operation (Operation *op); +void dump_perm_table (int **perm_table, int op_count); +void dump_entry_state (Entry_State *entry); +void dump_sv_attr_state (SV_Attr_State *sv_attr); +void dump_mv_attr_state (MV_Attr_State *mv_attr); +void dump_value_state (Value_State *value, int sv_attr); +void dump_dn_csn_list (Entry_State *entry); + +/* misc functions */ +int max_val (int a, int b); + +int main (int argc, char **argv) +{ + int i; + + process_cmd (argc, argv); + + for (i = 0; i < sim.runs; i++) + { + fprintf (sim.fout, "*******running simulation #%d ...\n\n", i+1); + run_simulation (); + fprintf (sim.fout, "\n*******done with simulation #%d ...\n\n", i+1); + } + + if (sim.fout != stdout) + fclose (sim.fout); + + return 0; +} + +void process_cmd (int argc, char **argv) +{ + int i; + + set_default_sim_params (); + + if (argc == 1) + { + return; + } + + if (strcmp (argv[1], "-h") == 0) /* print help */ + { + print_usage (); + exit (0); + } + + i = 1; + while (i < argc) + { + if (strcmp (argv[i], "-v") == 0) /* verbose mode */ + { + sim.verbose = 1; + i ++; + } + else if (strcmp (argv[i], "-n") == 0) + { + if (i < argc - 1) + { + int runs = atoi (argv[i + 1]); + if (runs > 0) + sim.runs = runs; + i+=2; + } + else + { + /* ONREPL print warning */ + i++; + } + } + else if (strcmp (argv[i], "-f") == 0) /* output file */ + { + if (i < argc - 1) + { + FILE *f = fopen (argv[i + 1], "w"); + if (f == 0) + { + printf ("failed to open output file; error - %s, using stdout\n", + strerror(errno)); + } + else + { + /* ONREPL print warning */ + sim.fout = f; + } + + i += 2; + } + else + i++; + } + else if (strcmp (argv[i], "-o") == 0) /* file with operation sequence */ + { + if (i < argc - 1) + { + parse_operations_file (argv[i+1]); + i += 2; + } + else + { + /* ONREPL print warning */ + i ++; + } + } + else /* unknown option */ + { + printf ("unknown option - %s; ignored\n", argv[i]); + i ++; + } + + } +} + +void set_default_sim_params () +{ + memset (&sim, 0, sizeof (sim)); + sim.runs = 1; + sim.fout = stdout; + sim.value_count = count_values (); +} + +/* file format: <operation count> + add <attribute> <value> + delete <attribute>[ <value>] + rename to <attribute> <value>[ delete <attribute> <value>] + + all spaces are significant + */ +void parse_operations_file (char *name) +{ + FILE *file = fopen (name, "r"); + char line [256]; + int i; + + if (file == NULL) + { + printf ("failed to open operations file %s: error = %d\n", name, errno); + print_usage (); + exit (1); + } + + i = 0; + while (fgets (line, sizeof (line), file)) + { + if (i == 0) + { + /* read operation count */ + sim.op_count = atoi (line); + if (sim.op_count < 1 || sim.op_count > MAX_OPS/2) + { + printf ("invalid operation count - %d; value must be between 1 and %d\n", + sim.op_count, MAX_OPS/2); + print_usage (); + exit (1); + } + else + { + sim.ops = (Operation*)malloc (sim.op_count * sizeof (Operation)); + } + } + else + { + if (strlen (line) == 0) /* skip empty lines */ + continue; + parse_operation (line, i); + } + + i ++; + } +} + +#define ADD_KEYWORD "add " +#define DELETE_KEYWORD "delete " +#define RENAME_KEYWORD "rename to " +#define DELET_OLD_RDN_KEYWORD " delete " + +void parse_operation (char *line, int i) +{ + int rc = 0; + char *pos; + char buff [64]; + + sim.ops [i - 1].csn = i; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + /* add <attribute> <value> */ + if (strncmp (line, ADD_KEYWORD, strlen (ADD_KEYWORD)) == 0) + { + sim.ops [i - 1].type = OP_ADD_VALUE; + pos = strchr (&line[strlen (ADD_KEYWORD)], ' '); + if (pos == NULL) + { + rc = -1; + goto done; + } + + memset (buff, 0, sizeof (buff)); + strncpy (buff, &line[strlen (ADD_KEYWORD)], pos - &line[strlen (ADD_KEYWORD)]); + sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME); + sim.ops [i - 1].value_index = value2index (pos + 1); + } + /* delete <attribute>[ <value>] */ + else if (strncmp (line, DELETE_KEYWORD, strlen (DELETE_KEYWORD)) == 0) + { + pos = strchr (&line[strlen (DELETE_KEYWORD)], ' '); + if (pos == NULL) /* delete attribute version */ + { + sim.ops [i - 1].type = OP_DELETE_ATTR; + sim.ops [i - 1].sv_attr = strcmp (&line[strlen (DELETE_KEYWORD)], + MV_ATTR_NAME); + } + else /* delete value version */ + { + memset (buff, 0, sizeof (buff)); + sim.ops [i - 1].type = OP_DELETE_VALUE; + strncpy (buff, &line[strlen (DELETE_KEYWORD)], + pos - &line[strlen (DELETE_KEYWORD)]); + sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME); + sim.ops [i - 1].value_index = value2index (pos + 1); + } + } + /* rename to <attribute> <valued>[ delete <attribute> <value>] */ + else if (strncmp (line, RENAME_KEYWORD, 10) == 0) + { + char *pos2; + + sim.ops [i - 1].type = OP_RENAME_ENTRY; + + pos = strchr (&line[strlen (RENAME_KEYWORD)], ' '); + if (pos == NULL) + { + rc = -1; + goto done; + } + + memset (buff, 0, sizeof (buff)); + strncpy (buff, &line[strlen (RENAME_KEYWORD)], pos - &line[strlen (RENAME_KEYWORD)]); + sim.ops [i - 1].sv_attr = strcmp (buff, MV_ATTR_NAME); + + pos2 = strstr (pos + 1, DELET_OLD_RDN_KEYWORD); + if (pos2 == NULL) /* no delete old rdn part */ + { + sim.ops [i - 1].value_index = value2index (pos + 1); + sim.ops [i - 1].delete_old_rdn = 0; + } + else + { + memset (buff, 0, sizeof (buff)); + strncpy (buff, pos + 1, pos2 - pos - 1); + sim.ops [i - 1].value_index = value2index (buff); + pos2 += strlen (DELET_OLD_RDN_KEYWORD); + pos = strchr (pos2, ' '); + if (pos == NULL) + { + rc = -1; + goto done; + } + + memset (buff, 0, sizeof (buff)); + strncpy (buff, pos2, pos - pos2); + sim.ops [i - 1].delete_old_rdn = 1; + sim.ops [i - 1].old_rdn_sv_attr = strcmp (buff, MV_ATTR_NAME); + sim.ops [i - 1].old_rdn_value_index = value2index (pos + 1); + } + } + else + { + /* error */ + rc = -1; + } + +done: + if (rc) + { + /* invalid line */ + printf ("invalid operation: %s\n", line); + exit (1); + } +} +int value2index (char *value) +{ + int i; + + for (i = 0; i < sim.value_count; i++) + { + if (strcmp (g_values[i], value) == 0) + return i; + } + + return -1; +} + +void print_usage () +{ + printf ("usage: dnp_sim [-h] [-n <number of simulations> ] [-v] [-f <output file>]\n" + "\t-h - print usage information\n" + "\t-n <number of simulations>; default - 1\n" + "\t-v - verbose mode\n" + "\t-f <output file> - by default, results are printed to the screen\n" + "\t-o <op file> - file that contains operation sequence to execute;\n" + "\tby default, random sequence is generated.\n"); +} + +int count_values () +{ + int i; + + for (i = 0; g_values[i]; i++); + + return i; +} + +void run_simulation () +{ + int *order; + int i; + int perm_count; + Entry_State entry_first, entry_current; + int error = 0; + + init_entry_state (&entry_first); + fprintf (sim.fout, "initial entry state :\n"); + dump_entry_state (&entry_first); + + if (sim.ops == NULL) + { + generate_operations (&sim.ops, &sim.op_count); + } + fprintf (sim.fout, "initial operation set:\n"); + dump_operations (sim.ops, sim.op_count, NULL/* order */); + + perm_count = get_perm_count (sim.op_count); + for (i = 0; i < perm_count; i++) + { + fprintf (sim.fout, "--------------------------------\n"); + fprintf (sim.fout, "simulation run %d\n", i + 1); + fprintf (sim.fout, "--------------------------------\n"); + order = generate_operation_order (sim.op_count, i); + if (i == 0) + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_first); + else + { + apply_operation_sequence (sim.ops, sim.op_count, order, &entry_current); + error |= compare_entry_state (&entry_first, &entry_current, i + 1); + } + } + + switch (error) + { + case 0: fprintf (sim.fout, "all runs left the entry in the same state\n"); + break; + case 1: fprintf (sim.fout, "while value presence is consistent across all runs, " + "the exact state does not match\n"); + break; + case 3: fprintf (sim.fout, "the runs left entries in an inconsistent state\n"); + break; + } + + free_operations (&sim.ops); +} + +void generate_operations (Operation **ops, int *op_count) +{ + int i; + + /* generate number operations in the sequence */ + *op_count = slapi_rand () % (MAX_OPS / 2) + 1; + *ops = (Operation *)malloc (*op_count * sizeof (Operation)); + + for (i = 0; i < *op_count; i ++) + { + generate_operation (&((*ops)[i]), i + 1); + } +} + +void generate_operation (Operation *op, int csn) +{ + /* generate operation type */ + op->type = slapi_rand () % OP_END; + op->csn = csn; + + /* choose if the operation applies to the single value or + the multivalued attribute */ + op->sv_attr = slapi_rand () % 2; + + /* generate value to which operation applies */ + op->value_index = slapi_rand () % sim.value_count; + + if (op->type == OP_RENAME_ENTRY) + { + op->delete_old_rdn = slapi_rand () % 2; + if (op->delete_old_rdn) + { + op->old_rdn_sv_attr = slapi_rand () % 2; + op->old_rdn_value_index = slapi_rand () % sim.value_count; + + while (op->old_rdn_sv_attr == op->sv_attr && + op->old_rdn_value_index == op->value_index) + { + op->old_rdn_sv_attr = slapi_rand () % 2; + op->old_rdn_value_index = slapi_rand () % sim.value_count; + } + } + } +} + +int* generate_operation_order (int op_count, int seq_num) +{ + static int **perm_table = NULL; + + /* first request - generate pemutation table */ + if (seq_num == 0) + { + int elements [MAX_OPS]; + int i; + + if (perm_table) + free_perm_table (&perm_table, op_count); + perm_table = new_perm_table (op_count); + + for (i = 0; i < op_count; i++) + elements [i] = i; + + generate_perm_table (elements, op_count, 0 /* static part */, + perm_table); + } + + return perm_table [seq_num]; +} + +void apply_operation_sequence (Operation *ops, int op_count, int *order, Entry_State *entry) +{ + int i; + + init_entry_state (entry); + + if (!sim.verbose) + { + if (!sim.verbose) + { + fprintf (sim.fout, "operation_sequence for this run:\n"); + dump_operations (ops, op_count, order); + } + } + + for (i = 0; i < op_count; i++) + { + apply_operation (entry, &(ops [order[i]])); + } + + if (!sim.verbose) + { + fprintf (sim.fout, "final entry state :\n"); + dump_entry_state (entry); + } +} + +void init_entry_state (Entry_State *entry) +{ + memset (entry, 0, sizeof (*entry)); + entry->dn_csn_count = 1; + + init_sv_attr_state (&entry->sv_attr); + init_mv_attr_state (&entry->mv_attr); +} + +void init_sv_attr_state (SV_Attr_State *sv_attr) +{ + memset (sv_attr, 0, sizeof (*sv_attr)); + sv_attr->attr_state.delete_csn = NOT_PRESENT; + sv_attr->attr_state.present = 1; + init_value_state (&sv_attr->current_value, 1); +} + +void init_mv_attr_state (MV_Attr_State *mv_attr) +{ + int i; + + memset (mv_attr, 0, sizeof (*mv_attr)); + mv_attr->attr_state.delete_csn = NOT_PRESENT; + mv_attr->attr_state.present = 1; + mv_attr->value_count = sim.value_count; + + for (i = 0; i < mv_attr->value_count; i++) + { + init_value_state (&(mv_attr->values[i]), i); + } +} + +void init_value_state (Value_State *val, int seq_num) +{ + memset (val, 0, sizeof (*val)); + val->value_index = seq_num; + val->present = 1; + val->delete_csn = NOT_PRESENT; +} + +void apply_operation (Entry_State *entry, Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: apply_add_operation (entry, op); + break; + + case OP_DELETE_VALUE: apply_value_delete_operation (entry, op); + break; + + case OP_DELETE_ATTR: apply_attr_delete_operation (entry, op); + break; + + case OP_RENAME_ENTRY: apply_rename_operation (entry, op); + break; + } + + if (sim.verbose) + { + fprintf (sim.fout, "operation: "); + dump_operation (op); + fprintf (sim.fout, "\n"); + dump_entry_state (entry); + } +} + +void free_operations (Operation **ops) +{ + free (*ops); + *ops = NULL; +} + +int **new_perm_table (int op_count) +{ + int i; + int **perm_table; + int perm_count = get_perm_count (op_count); + + perm_table = (int**)malloc (perm_count * sizeof (int*)); + for (i = 0; i < perm_count; i ++) + perm_table [i] = (int*) malloc (op_count * sizeof (int)); + + return perm_table; +} + +void free_perm_table (int ***perm_table, int op_count) +{ + int i; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < perm_count; i ++) + free ((*perm_table)[i]); + + free (*perm_table); + *perm_table = NULL; +} + +void generate_perm_table (int *elements, int element_count, int static_part, + int **perm_table) +{ + int i; + int elements_copy [MAX_OPS]; + int start_pos; + + if (element_count - 1 == static_part) + { + memcpy (*perm_table, elements, element_count * sizeof (int)); + return; + } + + start_pos = 0; + for (i = 0; i < element_count - static_part; i ++) + { + memcpy (elements_copy, elements, element_count * sizeof (int)); + elements_copy [static_part] = elements [static_part + i]; + elements_copy [static_part + i] = elements [static_part]; + generate_perm_table (elements_copy, element_count, static_part + 1, + &perm_table [start_pos]); + start_pos += get_perm_count (element_count - static_part - 1); + } +} + +int get_perm_count (int op_count) +{ + int i; + int perm_count = 1; + + for (i = 2; i <= op_count; i ++) + perm_count *= i; + + return perm_count; +} + +void apply_add_operation (Entry_State *entry, Operation *op) +{ + if (op->sv_attr) + { + Value_State *val; + Value_State temp_val; + + if (op->value_index == entry->sv_attr.current_value.value_index) + { + val = &entry->sv_attr.current_value; + } + else if (entry->sv_attr.pending_value && + op->value_index == entry->sv_attr.pending_value->value_index) + { + val = entry->sv_attr.pending_value; + } + else /* new value */ + { + init_value_state (&temp_val, op->value_index); + val = &temp_val; + } + + if (val->presence_csn < op->csn) + val->presence_csn = op->csn; + + resolve_sv_attr_state (entry, val); + } + else + { + if (entry->mv_attr.values[op->value_index].presence_csn < op->csn) + { + entry->mv_attr.values[op->value_index].presence_csn = op->csn; + resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index])); + } + } +} + +void apply_value_delete_operation (Entry_State *entry, Operation *op) +{ + if (op->sv_attr) + { + if (entry->sv_attr.attr_state.delete_csn < op->csn) + { + entry->sv_attr.attr_state.delete_csn = op->csn; + resolve_sv_attr_state (entry, NULL); + } + } + else /* mv attr */ + { + if (entry->mv_attr.values[op->value_index].delete_csn < op->csn) + { + entry->mv_attr.values[op->value_index].delete_csn = op->csn; + resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index])); + } + } +} + +void apply_attr_delete_operation (Entry_State *entry, Operation *op) +{ + int i; + + if (op->sv_attr) + { + if (entry->sv_attr.attr_state.delete_csn < op->csn) + { + entry->sv_attr.attr_state.delete_csn = op->csn; + resolve_sv_attr_state (entry, NULL); + } + } + else /* mv attr */ + { + if (entry->mv_attr.attr_state.delete_csn < op->csn) + { + entry->mv_attr.attr_state.delete_csn = op->csn; + + for (i = 0; i < sim.value_count; i++) + { + resolve_mv_attr_state (entry, &(entry->mv_attr.values[i])); + } + } + } +} + +void apply_rename_operation (Entry_State *entry, Operation *op) +{ + int index; + Operation del_op; + + /* insert new dn into dn_csn_list */ + index = dn_csn_add (entry, op->sv_attr, op->value_index, op->csn); + + /* issue delete value operation for the old rdn */ + if (op->delete_old_rdn) + { + del_op.type = OP_DELETE_VALUE; + del_op.csn = op->csn; + del_op.sv_attr = op->old_rdn_sv_attr; + del_op.value_index = op->old_rdn_value_index; + + apply_value_delete_operation (entry, &del_op); + } + + /* resolve state of the previous node in dn_csn_list */ + if (index > 0) + { + if (entry->dn_csns[index-1].sv_attr) + { + if (entry->dn_csns[index-1].value_index == + entry->sv_attr.current_value.value_index) + { + resolve_sv_attr_state (entry, &(entry->sv_attr.current_value)); + } + else if (entry->sv_attr.pending_value && + entry->dn_csns[index-1].value_index == + entry->sv_attr.pending_value->value_index) + { + resolve_sv_attr_state (entry, entry->sv_attr.pending_value); + } + } + else + { + int i = entry->dn_csns[index-1].value_index; + resolve_mv_attr_state (entry, &(entry->mv_attr.values[i])); + } + } + + /* resolve state of the new dn */ + if (op->sv_attr) + { + Value_State *value; + Value_State temp_val; + if (op->value_index == entry->sv_attr.current_value.value_index) + { + value = &entry->sv_attr.current_value; + } + else if (entry->sv_attr.pending_value && + op->value_index == entry->sv_attr.pending_value->value_index) + { + value = entry->sv_attr.pending_value; + } + else /* new value */ + { + init_value_state (&temp_val, op->value_index); + value = &temp_val; + } + + if (value->presence_csn == NOT_PRESENT || value->presence_csn < op->csn) + value->presence_csn = op->csn; + resolve_sv_attr_state (entry, value); + } + else + { + if (entry->mv_attr.values[op->value_index].presence_csn == NOT_PRESENT || + entry->mv_attr.values[op->value_index].presence_csn < op->csn) + entry->mv_attr.values[op->value_index].presence_csn = op->csn; + + resolve_mv_attr_state (entry, &(entry->mv_attr.values[op->value_index])); + } +} + +void purge_mv_attr_state (Entry_State *entry, Value_State *value) +{ + if (value->presence_csn > value->delete_csn) + value->delete_csn = NOT_PRESENT; +} + +void purge_sv_attr_state (Entry_State *entry) +{ + if (entry->sv_attr.attr_state.delete_csn != NOT_PRESENT) + { + if (entry->sv_attr.pending_value) + { + if (entry->sv_attr.attr_state.delete_csn < + entry->sv_attr.pending_value->presence_csn) + { + entry->sv_attr.attr_state.delete_csn = NOT_PRESENT; + } + } + else + { + if (entry->sv_attr.attr_state.delete_csn < + entry->sv_attr.current_value.presence_csn) + entry->sv_attr.attr_state.delete_csn = NOT_PRESENT; + } + } +} + +void resolve_mv_attr_state (Entry_State *entry, Value_State *value) +{ + purge_mv_attr_state (entry, value); + + /* no deletes that effect the state */ + if (max_val (value->delete_csn, entry->mv_attr.attr_state.delete_csn) < + value->presence_csn) + { + value->present = 1; + return; + } + + if (value->present) /* check if it should be removed based on the current state */ + { + if (!value_distinguished_at_csn (entry, 0, value, + max (value->delete_csn, entry->mv_attr.attr_state.delete_csn))) + { + value->present = 0; + } + } + else /* not present - check if it should be restored */ + { + if (value_distinguished_at_csn (entry, 0, value, + max (value->delete_csn, entry->mv_attr.attr_state.delete_csn))) + { + value->present = 1; + } + } + + if (entry->mv_attr.attr_state.delete_csn == NOT_PRESENT) + { + entry->mv_attr.attr_state.present = 1; + } + else + { + int i; + int distinguished = 0; + + for (i = 0; i < entry->mv_attr.value_count; i ++) + { + distinguished |= value_distinguished_at_csn (entry, 0, + &(entry->mv_attr.values[i]), + entry->mv_attr.attr_state.delete_csn); + } + + entry->mv_attr.attr_state.present = distinguished; + } +} + +void resolve_sv_attr_state (Entry_State *entry, Value_State *value) +{ + purge_sv_attr_state (entry); + + if (value) + { + /* existing value is modified */ + if (value == &(entry->sv_attr.current_value) || + value == entry->sv_attr.pending_value) + { + /* check if current value should be replaced with the pending value */ + if (entry->sv_attr.pending_value) + { + if (!value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value, + entry->sv_attr.current_value.presence_csn)) + { + /* replace current value with the pending value */ + memcpy (&entry->sv_attr.current_value, entry->sv_attr.pending_value, + sizeof (Value_State)); + free (entry->sv_attr.pending_value); + entry->sv_attr.pending_value = NULL; + } + } + } + else /* addition of a new value */ + { + /* new value is before the current value; note that, for new value, + presence_csn is the same as distinguished_csn */ + if (value->presence_csn < entry->sv_attr.current_value.presence_csn) + { + /* if new value is distinguished, it should become current and the + current can become pending */ + if (value_distinguished_at_csn (entry, 1, value, + entry->sv_attr.current_value.presence_csn)) + { + if (entry->sv_attr.pending_value == NULL) + { + entry->sv_attr.pending_value = (Value_State*) + malloc (sizeof (Value_State)); + memcpy (entry->sv_attr.pending_value, &entry->sv_attr.current_value, + sizeof (Value_State)); + } + + memcpy (&entry->sv_attr.current_value, value, sizeof (Value_State)); + } + } + else /* new value is after the current value */ + { + /* if current value is not distinguished, new value should + become distinguished */ + if (!value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value, + value->presence_csn)) + { + memcpy (&entry->sv_attr.current_value, value, sizeof (Value_State)); + } + else /* current value is distinguished - check if new value should replace + the pending value */ + { if (entry->sv_attr.pending_value) + { + if (value->presence_csn > entry->sv_attr.pending_value->presence_csn) + { + memcpy (entry->sv_attr.pending_value, value, sizeof (Value_State)); + } + } + else + { + entry->sv_attr.pending_value = (Value_State*)malloc (sizeof (Value_State)); + memcpy (entry->sv_attr.pending_value, value, sizeof (Value_State)); + } + } + } + } + } + + /* update the attribute state */ + purge_sv_attr_state (entry); + + /* set attribute state */ + if (entry->sv_attr.attr_state.delete_csn != NOT_PRESENT && + !value_distinguished_at_csn (entry, 1, &entry->sv_attr.current_value, + entry->sv_attr.attr_state.delete_csn)) + { + entry->sv_attr.attr_state.present = 0; + } + else + { + entry->sv_attr.attr_state.present = 1; + } +} + +int value_distinguished_at_csn (Entry_State *entry, int sv_attr, Value_State *value, int csn) +{ + int i; + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > csn) + break; + } + + /* i is never equal to 0 because the csn of the first element is always + smaller than csn of any operation we can receive */ + return (entry->dn_csns[i-1].value_index == value->value_index && + entry->dn_csns[i-1].sv_attr == sv_attr); +} + +int compare_entry_state (Entry_State *entry1, Entry_State *entry2, int run) +{ + int i; + int error = 0; + + error = compare_entry_state_quick (entry1, entry2, run); + + if (error) + return 3; + + /* compare dnc_csn list */ + if (entry1->dn_csn_count != entry2->dn_csn_count) + { + fprintf (sim.fout, "dn_csn count is %d for run 1 and %d for run %d\n", + entry1->dn_csn_count, entry2->dn_csn_count, run); + error = 1; + } + + for (i = 0; i < entry1->dn_csn_count; i++) + { + if (entry1->dn_csns [i].csn != entry2->dn_csns [i].csn || + entry1->dn_csns [i].sv_attr != entry2->dn_csns [i].sv_attr || + entry1->dn_csns [i].value_index != entry2->dn_csns [i].value_index) + { + fprintf (sim.fout,"elements %d of dn csn list are different:\n" + "\tfirst run: csn - %d, attr - %s, value - %s\n" + "\t%d run: csn - %d, attr - %s value - %s\n", i, + entry1->dn_csns [i].csn, + entry1->dn_csns [i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values[entry1->dn_csns [i].value_index], + run, entry2->dn_csns [i].csn, + entry2->dn_csns [i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values[entry2->dn_csns [i].value_index]); + + error = 1; + } + } + + error |= compare_sv_attr_state (&entry1->sv_attr, &entry2->sv_attr, run); + + error |= compare_mv_attr_state (&entry1->mv_attr, &entry2->mv_attr, run); + + if (error != 0) + { + return 1; + } + else + return 0; +} + +/* just compare if the same attributes and values are present */ +int compare_entry_state_quick (Entry_State *entry1, Entry_State *entry2, int run) +{ + int error; + + error = compare_sv_attr_state_quick (&entry1->sv_attr, &entry2->sv_attr, run); + + error |= compare_mv_attr_state_quick (&entry1->mv_attr, &entry2->mv_attr, run); + + return error; +} + +int compare_sv_attr_state_quick (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run) +{ + int error = 0; + if (sv_attr1->attr_state.present != sv_attr2->attr_state.present) + { + fprintf (sim.fout, "singlevalued attribute is %s present in the first run " + "but is %s present in the %d run\n", + sv_attr1->attr_state.present ? "" : "not", + sv_attr2->attr_state.present ? "" : "not", run); + return 1; + } + + if (sv_attr1->attr_state.present && + sv_attr1->current_value.value_index != sv_attr2->current_value.value_index) + { + fprintf (sim.fout, "different values for singlevalued attribute: %s for the \n" + "first run and %s for the %d run\n", + g_values [sv_attr1->current_value.value_index], + g_values [sv_attr2->current_value.value_index], run); + return 1; + } + + return 0; +} + +int compare_mv_attr_state_quick (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run) +{ + int i; + int error = 0; + + if (mv_attr1->attr_state.present != mv_attr2->attr_state.present) + { + fprintf (sim.fout, "multivalued attribute is %s present in the first run " + "but is %s present in the %d run\n", + mv_attr1->attr_state.present ? "" : "not", + mv_attr2->attr_state.present ? "" : "not", run); + return 1; + } + + /* value count does not change during the iteration, so we don't have + to check if the count is the same for both attributes */ + for (i = 0; i < mv_attr1->value_count; i++) + { + if (mv_attr1->values[i].present != mv_attr2->values[i].present) + { + fprintf (sim.fout, "value %s is %s present in the multivalued attribute\n" + "in the first run but %s present in the %d run\n", + g_values[i], mv_attr1->values[i].present ? "" : "not", + mv_attr2->values[i].present ? "" : "not", run); + error = 1; + } + } + + return error; +} + +int compare_sv_attr_state (SV_Attr_State *sv_attr1, SV_Attr_State *sv_attr2, int run) +{ + int error = 0; + + if (sv_attr1->attr_state.delete_csn != sv_attr2->attr_state.delete_csn) + { + fprintf (sim.fout, "singlevalued attribute deletion csn is %d for run 1 " + "but is %d for run %d\n", sv_attr1->attr_state.delete_csn, + sv_attr2->attr_state.delete_csn, run); + error = 1; + } + + error |= compare_value_state (&sv_attr1->current_value, &sv_attr2->current_value, run); + + if ((sv_attr1->pending_value && !sv_attr1->pending_value) || + (!sv_attr1->pending_value && sv_attr1->pending_value)) + { + fprintf (sim.fout, "pending value is %s present in the singlevalued attribute\n" + " in the first run but is %s in the %d run\n", + sv_attr1->pending_value ? "" : "not", + sv_attr2->pending_value ? "" : "not", run); + + return 1; + } + + if (sv_attr1->pending_value) + error |= compare_value_state (sv_attr1->pending_value, sv_attr2->pending_value, run); + + return 0; +} + +int compare_mv_attr_state (MV_Attr_State *mv_attr1, MV_Attr_State *mv_attr2, int run) +{ + int error = 0; + int i; + + if (mv_attr1->attr_state.delete_csn != mv_attr2->attr_state.delete_csn) + { + fprintf (sim.fout, "multivalued attribute deletion csn is %d for run 1 " + "but is %d for run %d\n", mv_attr1->attr_state.delete_csn, + mv_attr2->attr_state.delete_csn, run); + error = 1; + } + + for (i = 0; i < mv_attr1->value_count; i++) + { + error |= compare_value_state (&mv_attr1->values[i], &mv_attr2->values[i], run); + } + + return error; +} + +int compare_value_state (Value_State *value1, Value_State *value2, int run) +{ + int error = 0; + + if (value1->presence_csn != value2->presence_csn) + { + fprintf (sim.fout, "multivalued attribute: presence csn for value %s is %d " + "in run 1 but is %d in run %d\n", g_values[value1->value_index], + value1->presence_csn, value2->presence_csn, run); + error = 1; + } + + if (value1->delete_csn != value2->delete_csn) + { + fprintf (sim.fout, "multivalued attribute: delete csn for value %s is %d in run 1 " + "but is %d in run %d\n", g_values[value1->value_index], + value1->delete_csn, value2->delete_csn, run); + error = 1; + } + + return error; +} + +int dn_csn_add (Entry_State *entry, int sv_attr, int value_index, int csn) +{ + int i; + + for (i = 0; i < entry->dn_csn_count; i++) + { + if (entry->dn_csns[i].csn > csn) + break; + } + + if (i < entry->dn_csn_count) + { + memcpy (&(entry->dn_csns[i+1]), &(entry->dn_csns[i]), + (entry->dn_csn_count - i) * sizeof (Dn_Csn)); + } + + entry->dn_csns[i].csn = csn; + entry->dn_csns[i].sv_attr = sv_attr; + entry->dn_csns[i].value_index = value_index; + entry->dn_csn_count ++; + + return i; +} + +void dump_operations (Operation *ops, int op_count, int *order) +{ + int index; + int i; + + for (i = 0; i < op_count; i ++) + { + if (order == NULL) /* current order */ + index = i; + else + index = order [i]; + + dump_operation (&ops[index]); + } + + fprintf (sim.fout, "\n"); +} + +void dump_operation (Operation *op) +{ + switch (op->type) + { + case OP_ADD_VALUE: + fprintf (sim.fout, "\t%d add value %s to %s\n", op->csn, + g_values [op->value_index], + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME); + break; + case OP_DELETE_VALUE: + fprintf (sim.fout, "\t%d delete value %s from %s\n", op->csn, + g_values [op->value_index], + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME); + break; + case OP_DELETE_ATTR: + fprintf (sim.fout, "\t%d delete %s attribute\n", op->csn, + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME); + break; + case OP_RENAME_ENTRY: + fprintf (sim.fout, "\t%d rename entry to %s=%s", op->csn, + op->sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values [op->value_index]); + if (op->delete_old_rdn) + fprintf (sim.fout, " delete old rdn %s=%s\n", + op->old_rdn_sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values [op->old_rdn_value_index]); + else + fprintf (sim.fout, "\n"); + break; + } +} + +void dump_perm_table (int **perm_table, int op_count) +{ + int i, j; + int perm_count = get_perm_count (op_count); + + for (i = 0; i < op_count; i++) + { + for (j = 0; j < perm_count; j++) + { + fprintf (sim.fout, "%d ", perm_table [j][i]); + } + + fprintf (sim.fout, "\n"); + } +} + +void dump_entry_state (Entry_State *entry) +{ + dump_dn_csn_list (entry); + + dump_sv_attr_state (&entry->sv_attr); + dump_mv_attr_state (&entry->mv_attr); + + fprintf (sim.fout, "\n"); +} + +void dump_sv_attr_state (SV_Attr_State *sv_attr) +{ + fprintf (sim.fout, "\tattribute %s is %s present", SV_ATTR_NAME, + sv_attr->attr_state.present ? "" : "not"); + if (sv_attr->attr_state.present) + { + fprintf (sim.fout, " and has the value of %s\n", + g_values[sv_attr->current_value.value_index]); + } + else + { + fprintf (sim.fout, "\n"); + } + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tdeletion csn: %d\n", sv_attr->attr_state.delete_csn); + fprintf (sim.fout, "\t\tcurrent value: "); + dump_value_state (&sv_attr->current_value, 1/* for single valued attr */); + if (sv_attr->pending_value) + { + fprintf (sim.fout, "\t\tpending value: "); + dump_value_state (sv_attr->pending_value, 1/* for single valued attr */); + } + } +} + +void dump_mv_attr_state (MV_Attr_State *mv_attr) +{ + int i; + + fprintf (sim.fout, "\tattribute %s is %s present\n", MV_ATTR_NAME, + mv_attr->attr_state.present ? "" : "not"); + + if (sim.verbose) + { + fprintf (sim.fout, "\t\tdeletion csn: %d\n", mv_attr->attr_state.delete_csn); + } + + for (i = 0; i < mv_attr->value_count; i++) + { + dump_value_state (&(mv_attr->values[i]), 0); + } +} + +void dump_value_state (Value_State *value, int sv_attr) +{ + if (!sv_attr) + { + fprintf (sim.fout, "\tvalue %s is %s present\n", g_values[value->value_index], + value->present ? "" : "not"); + } + else + { + fprintf (sim.fout, "%s\n", g_values[value->value_index]); + } + + if (sim.verbose) + { + fprintf (sim.fout, "\t\t\tpresence csn: %d\n", value->presence_csn); + fprintf (sim.fout, "\t\t\tdeletion value csn: %d\n", value->delete_csn); + } +} + +void dump_dn_csn_list (Entry_State *entry) +{ + int i; + + fprintf (sim.fout, "\tdn csn list: \n"); + for (i = 0; i < entry->dn_csn_count; i++) + { + fprintf (sim.fout, "\t\t %s=%s, csn: %d\n", + entry->dn_csns[i].sv_attr ? SV_ATTR_NAME : MV_ATTR_NAME, + g_values[entry->dn_csns[i].value_index], entry->dn_csns[i].csn); + } +} + +/* misc functions */ +int max_val (int a, int b) +{ + if (a >= b) + return a; + else + return b; +} diff --git a/ldap/servers/plugins/replication/tests/makesim b/ldap/servers/plugins/replication/tests/makesim new file mode 100755 index 00000000..0cedd6e1 --- /dev/null +++ b/ldap/servers/plugins/replication/tests/makesim @@ -0,0 +1,58 @@ +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# gnu makefile for LDAP Server tools. +# + +MCOM_ROOT = ../../../../../.. +LDAP_SRC = ../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip + +OBJDEST = $(OBJDIR)/lib/replication-plugin +BINDIR = $(OBJDIR)/bin + +include $(MCOM_ROOT)/netsite/nsdefs.mk +include $(MCOM_ROOT)/netsite/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +LDFLAGS += $(EXLDFLAGS) + +ifeq ($(ARCH), WINNT) +SUBSYSTEM=console +endif + +DEPLIBS= + +EXTRA_LIBS_DEP = + +EXTRA_LIBS = + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS += user32.lib +endif + +DNP_SIM = $(addsuffix $(EXE_SUFFIX), \ + $(addprefix $(BINDIR)/, dnp_sim)) + + +all: $(OBJDEST) $(BINDIR) $(DNP_SIM) + +$(DNP_SIM): $(OBJDEST)/dnp_sim3.o $(EXTRA_LIBS_DEP) + $(LINK_EXE) $(OBJDEST)/dnp_sim3.o \ + $(EXTRA_LIBS) $< + + +$(OBJDEST): + $(MKDIR) $(OBJDEST) + +$(BINDIR): + $(MKDIR) $(BINDIR) + +clean: + -$(RM) $(ALL_OBJS) + -$(RM) $(BINS) 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; +} diff --git a/ldap/servers/plugins/replication/urp.h b/ldap/servers/plugins/replication/urp.h new file mode 100644 index 00000000..9db477bd --- /dev/null +++ b/ldap/servers/plugins/replication/urp.h @@ -0,0 +1,45 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + */ + +#define REASON_ANNOTATE_DN "namingConflict" +#define REASON_RESURRECT_ENTRY "deletedEntryHasChildren" + +/* + * urp.c + */ +int urp_modify_operation( Slapi_PBlock *pb ); +int urp_add_operation( Slapi_PBlock *pb ); +int urp_delete_operation( Slapi_PBlock *pb ); +int urp_post_delete_operation( Slapi_PBlock *pb ); +int urp_modrdn_operation( Slapi_PBlock *pb ); +int urp_post_modrdn_operation( Slapi_PBlock *pb ); + +/* urp internal ops */ +int urp_fixup_add_entry (Slapi_Entry *e, const char *target_uniqueid, const char *parentuniqueid, CSN *opcsn, int opflags); +int urp_fixup_delete_entry (const char *uniqueid, const char *dn, CSN *opcsn, int opflags); +int urp_fixup_rename_entry (Slapi_Entry *entry, const char *newrdn, int opflags); +int urp_fixup_modify_entry (const char *uniqueid, const char *dn, CSN *opcsn, Slapi_Mods *smods, int opflags); + +int is_suffix_dn (Slapi_PBlock *pb, const Slapi_DN *dn, Slapi_DN **parenddn); + +/* + * urp_glue.c + */ +int is_glue_entry(const Slapi_Entry* entry); +int create_glue_entry ( Slapi_PBlock *pb, char *sessionid, Slapi_DN *dn, const char *uniqueid, CSN *opcsn ); +int entry_to_glue(char *sessionid, const Slapi_Entry* entry, const char *reason, CSN *opcsn); +int glue_to_entry (Slapi_PBlock *pb, Slapi_Entry *entry ); +PRBool get_glue_csn(const Slapi_Entry *entry, const CSN **gluecsn); + +/* + * urp_tombstone.c + */ +int is_tombstone_entry(const Slapi_Entry* entry); +int tombstone_to_glue(Slapi_PBlock *pb, const char *sessionid, Slapi_Entry *entry, const Slapi_DN *parentdn, const char *reason, CSN *opcsn); +int entry_to_tombstone ( Slapi_PBlock *pb, Slapi_Entry *entry ); +PRBool get_tombstone_csn(const Slapi_Entry *entry, const CSN **delcsn); diff --git a/ldap/servers/plugins/replication/urp_glue.c b/ldap/servers/plugins/replication/urp_glue.c new file mode 100644 index 00000000..dcb2f72d --- /dev/null +++ b/ldap/servers/plugins/replication/urp_glue.c @@ -0,0 +1,235 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * urp_glue.c - Update Resolution Procedures - Glue + */ + +#include "slapi-plugin.h" +#include "repl5.h" +#include "urp.h" + + +#define RDNBUFSIZE 2048 +extern int slapi_log_urp; + +/* + * Check if the entry is glue. + */ +int +is_glue_entry(const Slapi_Entry* entry) +{ + /* JCMREPL - Is there a more efficient way to do this? */ + return slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "glue"); +} + +/* returns PR_TRUE if the entry is a glue entry, PR_FALSE otherwise + sets the gluecsn if it is a glue entry - gluecsn may (but should not) be NULL */ +PRBool +get_glue_csn(const Slapi_Entry *entry, const CSN **gluecsn) +{ + PRBool isglue = PR_FALSE; + Slapi_Attr *oc_attr = NULL; + + /* cast away const - entry */ + if (entry_attr_find_wsi((Slapi_Entry*)entry, SLAPI_ATTR_OBJECTCLASS, &oc_attr) == ATTRIBUTE_PRESENT) + { + Slapi_Value *glue_value = NULL; + struct berval v; + v.bv_val = "glue"; + v.bv_len = strlen(v.bv_val); + if (attr_value_find_wsi(oc_attr, &v, &glue_value) == VALUE_PRESENT) + { + isglue = PR_TRUE; + *gluecsn = value_get_csn(glue_value, CSN_TYPE_VALUE_UPDATED); + } + } + + return isglue; +} + +/* + * Submit a Modify operation to turn the Entry into Glue. + */ +int +entry_to_glue(char *sessionid, const Slapi_Entry* entry, const char *reason, CSN *opcsn) +{ + int op_result = 0; + const char *dn; + char ebuf[BUFSIZ]; + slapi_mods smods; + Slapi_Attr *attr; + + dn = slapi_entry_get_dn_const (entry); + slapi_mods_init(&smods, 4); + /* + richm: sometimes the entry is already a glue entry (how did that happen?) + OR + the entry is already objectclass extensibleObject or already has the + conflict attribute and/or value + */ + if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "glue")) + { + slapi_mods_add_string( &smods, LDAP_MOD_ADD, SLAPI_ATTR_OBJECTCLASS, "glue" ); + + if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "extensibleobject")) + slapi_mods_add_string( &smods, LDAP_MOD_ADD, SLAPI_ATTR_OBJECTCLASS, "extensibleobject" ); + } + else + { + slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, + "%s: Target entry %s is already a glue entry reason %s\n", + sessionid, escape_string(dn, ebuf), reason); + } + + if (slapi_entry_attr_find (entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) + { + slapi_mods_add_string( &smods, LDAP_MOD_REPLACE, ATTR_NSDS5_REPLCONFLICT, reason); + } + else + { + slapi_mods_add_string( &smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, reason); + } + + if (slapi_mods_get_num_mods(&smods) > 0) + { + op_result = urp_fixup_modify_entry (NULL, dn, opcsn, &smods, 0); + if (op_result == LDAP_SUCCESS) + { + slapi_log_error (slapi_log_urp, repl_plugin_name, + "%s: Turned the entry %s to glue, reason %s\n", + sessionid, escape_string(dn, ebuf), reason); + } + } + + slapi_mods_done(&smods); + return op_result; +} + +static const char *glue_entry = + "dn: %s\n" + "%s" + "objectclass: top\n" + "objectclass: extensibleObject\n" /* JCMREPL - To avoid schema checking. */ + "objectclass: glue\n" + "nsuniqueid: %s\n" + "%s: %s\n"; /* Add why it's been created */ + +static int +do_create_glue_entry(const Slapi_RDN *rdn, const Slapi_DN *superiordn, const char *uniqueid, const char *reason, CSN *opcsn) +{ + int op_result= LDAP_OPERATIONS_ERROR; + int rdnval_index = 0; + int rdntype_len, rdnval_len, rdnpair_len, rdnstr_len, alloc_len; + Slapi_Entry *e; + Slapi_DN *sdn = NULL; + Slapi_RDN *newrdn = slapi_rdn_new_rdn(rdn); + char *estr, *rdnstr, *rdntype, *rdnval, *rdnpair; + sdn = slapi_sdn_new_dn_byval(slapi_sdn_get_ndn(superiordn)); + slapi_sdn_add_rdn(sdn,rdn); + + + /* must take care of multi-valued rdn: split rdn into different lines introducing + * '\n' between each type/value pair. + */ + alloc_len = RDNBUFSIZE; + rdnstr = slapi_ch_malloc(alloc_len); + rdnpair = rdnstr; + *rdnpair = '\0'; /* so that strlen(rdnstr) may return 0 the first time it's called */ + while ((rdnval_index = slapi_rdn_get_next(newrdn, rdnval_index, &rdntype, &rdnval)) != -1) { + rdntype_len = strlen(rdntype); + rdnval_len = strlen(rdnval); + rdnpair_len = LDIF_SIZE_NEEDED(rdntype_len, rdnval_len); + rdnstr_len = strlen(rdnstr); + if ((rdnstr_len + rdnpair_len + 1) > alloc_len) { + alloc_len += (rdnpair_len + 1); + rdnstr = slapi_ch_realloc(rdnstr, alloc_len); + rdnpair = &rdnstr[rdnstr_len]; + } + ldif_put_type_and_value_with_options(&rdnpair, rdntype, + rdnval, rdnval_len, LDIF_OPT_NOWRAP); + *rdnpair = '\0'; + } + estr= slapi_ch_malloc(strlen(glue_entry) + slapi_sdn_get_ndn_len(sdn) + + strlen(rdnstr) + strlen(uniqueid) + + strlen(ATTR_NSDS5_REPLCONFLICT) + strlen(reason) + 1); + sprintf(estr, glue_entry, slapi_sdn_get_ndn(sdn), rdnstr, uniqueid, + ATTR_NSDS5_REPLCONFLICT, reason); + slapi_ch_free((void**)&rdnstr); + slapi_rdn_done(newrdn); + slapi_ch_free((void**)&newrdn); + e = slapi_str2entry( estr, 0 ); + PR_ASSERT(e!=NULL); + if ( e!=NULL ) + { + slapi_entry_set_uniqueid (e, slapi_ch_strdup(uniqueid)); + op_result = urp_fixup_add_entry (e, NULL, NULL, opcsn, 0); + slapi_ch_free ( (void **) &estr ); /* XXXggood - this leaks if e == NULL */ + } + slapi_sdn_free(&sdn); + return op_result; +} + +int +create_glue_entry ( Slapi_PBlock *pb, char *sessionid, Slapi_DN *dn, const char *uniqueid, CSN *opcsn ) +{ + int op_result; + const char *dnstr; + + if ( slapi_sdn_get_dn (dn) ) + dnstr = slapi_sdn_get_dn (dn); + else + dnstr = ""; + + if ( NULL == uniqueid ) + { + op_result = LDAP_OPERATIONS_ERROR; + slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Can't create glue %s, uniqueid=NULL\n", sessionid, dnstr); + } + else + { + Slapi_Backend *backend; + Slapi_DN *superiordn = slapi_sdn_new(); + Slapi_RDN *rdn= slapi_rdn_new(); + int done= 0; + + slapi_pblock_get( pb, SLAPI_BACKEND, &backend ); + slapi_sdn_get_backend_parent ( dn, superiordn, backend ); + slapi_sdn_get_rdn ( dn, rdn ); + + while(!done) + { + op_result= do_create_glue_entry(rdn, superiordn, uniqueid, "missingEntry", opcsn); + switch(op_result) + { + case LDAP_SUCCESS: + slapi_log_error ( SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Created glue entry %s uniqueid=%s reason missingEntry\n", + sessionid, dnstr, uniqueid); + done= 1; + break; + case LDAP_NO_SUCH_OBJECT: + /* The parent is missing */ + { + /* JCMREPL - Create the parent ... recursion?... but what's the uniqueid? */ + PR_ASSERT(0); /* JCMREPL */ + } + default: + slapi_log_error ( SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Can't created glue entry %s uniqueid=%s, error %d\n", + sessionid, dnstr, uniqueid, op_result); + break; + } + /* JCMREPL - Could get trapped in this loop forever! */ + } + + slapi_rdn_free ( &rdn ); + slapi_sdn_free ( &superiordn ); + } + + return op_result; +} diff --git a/ldap/servers/plugins/replication/urp_tombstone.c b/ldap/servers/plugins/replication/urp_tombstone.c new file mode 100644 index 00000000..3b24b928 --- /dev/null +++ b/ldap/servers/plugins/replication/urp_tombstone.c @@ -0,0 +1,210 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * urp_tombstone.c - Update Resolution Procedures - Tombstones + */ + +#include "slapi-plugin.h" +#include "repl5.h" +#include "urp.h" + +extern int slapi_log_urp; + +/* + * Check if the entry is a tombstone. + */ +int +is_tombstone_entry(const Slapi_Entry* entry) +{ + int flag; + + /* LP: This doesn't work very well with entries that we tombstone ourself */ + flag = slapi_entry_flag_is_set (entry, SLAPI_ENTRY_FLAG_TOMBSTONE); + if (flag == 0) + { + /* This is slow */ + flag = slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, SLAPI_ATTR_VALUE_TOMBSTONE); + } + return flag; +} + +PRBool +get_tombstone_csn(const Slapi_Entry *entry, const CSN **delcsn) +{ + PRBool ists = PR_FALSE; + if (is_tombstone_entry(entry)) { + ists = PR_TRUE; + *delcsn = _get_deletion_csn((Slapi_Entry *)entry); /* cast away const */ + } + + return ists; +} + +static int +tombstone_to_glue_resolve_parent ( + Slapi_PBlock *pb, + const char *sessionid, + const Slapi_DN *parentdn, + const char *parentuniqueid, + CSN *opcsn) +{ + /* Let's have a look at the parent of this entry... */ + if(!slapi_sdn_isempty(parentdn) && parentuniqueid!=NULL) + { + int op_result; + Slapi_PBlock *newpb= slapi_pblock_new(); + slapi_search_internal_set_pb( + newpb, + slapi_sdn_get_dn(parentdn), /* JCM - This DN just identifies the backend to be searched. */ + LDAP_SCOPE_BASE, + "objectclass=*", + NULL, /*attrs*/ + 0, /*attrsonly*/ + NULL, /*Controls*/ + parentuniqueid, /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + slapi_search_internal_pb(newpb); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + switch(op_result) + { + case LDAP_SUCCESS: + { + Slapi_Entry **entries= NULL; + /* OK, the tombstone entry parent exists. Is it also a tombstone? */ + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if(entries!=NULL && entries[0]!=NULL) + { + if(is_tombstone_entry(entries[0])) + { + tombstone_to_glue (pb, sessionid, entries[0], parentdn, REASON_RESURRECT_ENTRY, opcsn); + } + } + else + { + /* JCM - Couldn't find the entry! */ + } + } + break; + default: + /* So, the tombstone entry had a parent... but it's gone. */ + /* That's probably a bad thing. */ + break; + } + slapi_free_search_results_internal (newpb); + slapi_pblock_destroy(newpb); + } + return 0; +} + +/* + * Convert a tombstone into a glue entry. + */ +int +tombstone_to_glue ( + Slapi_PBlock *pb, + const char *sessionid, + Slapi_Entry *tombstoneentry, + const Slapi_DN *tombstonedn, + const char *reason, + CSN *opcsn) +{ + Slapi_DN *parentdn; + char *parentuniqueid; + const char *tombstoneuniqueid; + Slapi_Entry *addingentry; + const char *addingdn; + int op_result; + + /* JCMREPL + * Nothing logged to the 5.0 Change Log + * Add is logged to the 4.0 Change Log - Core server Add code + * must attach the entry to the Operation + */ + + + /* Resurrect the parent entry first */ + + /* JCM - This DN calculation is odd. It could resolve to NULL + * which won't help us identify the correct backend to search. + */ + is_suffix_dn (pb, tombstonedn, &parentdn); + parentuniqueid= slapi_entry_attr_get_charptr (tombstoneentry, + SLAPI_ATTR_VALUE_PARENT_UNIQUEID); /* Allocated */ + tombstone_to_glue_resolve_parent (pb, sessionid, parentdn, parentuniqueid, opcsn); + slapi_sdn_free(&parentdn); + + /* Submit an Add operation to turn the tombstone entry into glue. */ + /* + * The tombstone is stored with an invalid DN, we must fix this. + */ + addingentry = slapi_entry_dup(tombstoneentry); + addingdn = slapi_sdn_get_dn(tombstonedn); + slapi_entry_set_dn(addingentry,slapi_ch_strdup(addingdn)); /* consumes DN */ + + if (!slapi_entry_attr_hasvalue(addingentry, ATTR_NSDS5_REPLCONFLICT, reason)) + { + /* Add the reason of turning it to glue - The backend code will use it*/ + slapi_entry_add_string(addingentry, ATTR_NSDS5_REPLCONFLICT, reason); + } + tombstoneuniqueid= slapi_entry_get_uniqueid(tombstoneentry); + op_result = urp_fixup_add_entry (addingentry, tombstoneuniqueid, parentuniqueid, opcsn, OP_FLAG_RESURECT_ENTRY); + if (op_result == LDAP_SUCCESS) + { + slapi_log_error (slapi_log_urp, repl_plugin_name, + "%s: Resurrected tombstone %s to glue reason '%s'\n", sessionid, addingdn, reason); + } + else + { + slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, + "%s: Can't resurrect tombstone %s to glue reason '%s', error=%d\n", + sessionid, addingdn, reason, op_result); + } + slapi_entry_free (addingentry); + return op_result; +} + +int +entry_to_tombstone ( Slapi_PBlock *pb, Slapi_Entry *entry ) +{ + Slapi_Operation *op; + Slapi_Mods smods; + CSN *opcsn; + const char *uniqueid; + int op_result = LDAP_SUCCESS; + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + opcsn = operation_get_csn ( op ); + uniqueid = slapi_entry_get_uniqueid ( entry ); + + + slapi_mods_init ( &smods, 2 ); + /* Remove objectclass=glue */ + slapi_mods_add ( &smods, LDAP_MOD_DELETE, SLAPI_ATTR_OBJECTCLASS, strlen("glue"), "glue"); + /* Remove any URP conflict since a tombstone shouldn't + * be retrieved later for conflict removal. + */ + slapi_mods_add ( &smods, LDAP_MOD_DELETE, ATTR_NSDS5_REPLCONFLICT, 0, NULL ); + + op_result = urp_fixup_modify_entry (uniqueid, slapi_entry_get_dn_const (entry), opcsn, &smods, 0); + slapi_mods_done ( &smods ); + + /* + * Delete the entry. + */ + if ( op_result == LDAP_SUCCESS ) + { + /* + * Using internal delete operation since it would go + * through the urp operations and trigger the recursive + * fixup if applicable. + */ + op_result = urp_fixup_delete_entry (uniqueid, slapi_entry_get_dn_const (entry), opcsn, 0); + } + + return op_result; +} |