summaryrefslogtreecommitdiffstats
path: root/ldap/servers/plugins/replication
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/servers/plugins/replication')
-rw-r--r--ldap/servers/plugins/replication/Makefile152
-rw-r--r--ldap/servers/plugins/replication/cl4.h65
-rw-r--r--ldap/servers/plugins/replication/cl4_api.c797
-rw-r--r--ldap/servers/plugins/replication/cl4_api.h67
-rw-r--r--ldap/servers/plugins/replication/cl4_init.c349
-rw-r--r--ldap/servers/plugins/replication/cl5.h38
-rw-r--r--ldap/servers/plugins/replication/cl5_api.c6512
-rw-r--r--ldap/servers/plugins/replication/cl5_api.h478
-rw-r--r--ldap/servers/plugins/replication/cl5_clcache.c910
-rw-r--r--ldap/servers/plugins/replication/cl5_clcache.h22
-rw-r--r--ldap/servers/plugins/replication/cl5_config.c868
-rw-r--r--ldap/servers/plugins/replication/cl5_init.c77
-rw-r--r--ldap/servers/plugins/replication/cl5_test.c830
-rw-r--r--ldap/servers/plugins/replication/cl5_test.h21
-rw-r--r--ldap/servers/plugins/replication/csnpl.c328
-rw-r--r--ldap/servers/plugins/replication/csnpl.h23
-rw-r--r--ldap/servers/plugins/replication/dllmain.c91
-rw-r--r--ldap/servers/plugins/replication/legacy_consumer.c707
-rw-r--r--ldap/servers/plugins/replication/llist.c336
-rw-r--r--ldap/servers/plugins/replication/llist.h26
-rw-r--r--ldap/servers/plugins/replication/profile.c42
-rw-r--r--ldap/servers/plugins/replication/repl.h366
-rw-r--r--ldap/servers/plugins/replication/repl5.h480
-rw-r--r--ldap/servers/plugins/replication/repl5_agmt.c1766
-rw-r--r--ldap/servers/plugins/replication/repl5_agmtlist.c618
-rw-r--r--ldap/servers/plugins/replication/repl5_backoff.c232
-rw-r--r--ldap/servers/plugins/replication/repl5_connection.c1493
-rw-r--r--ldap/servers/plugins/replication/repl5_inc_protocol.c1759
-rw-r--r--ldap/servers/plugins/replication/repl5_init.c572
-rw-r--r--ldap/servers/plugins/replication/repl5_mtnode_ext.c194
-rw-r--r--ldap/servers/plugins/replication/repl5_plugins.c1416
-rw-r--r--ldap/servers/plugins/replication/repl5_prot_private.h66
-rw-r--r--ldap/servers/plugins/replication/repl5_protocol.c502
-rw-r--r--ldap/servers/plugins/replication/repl5_protocol_util.c468
-rw-r--r--ldap/servers/plugins/replication/repl5_replica.c3387
-rw-r--r--ldap/servers/plugins/replication/repl5_replica_config.c750
-rw-r--r--ldap/servers/plugins/replication/repl5_replica_dnhash.c189
-rw-r--r--ldap/servers/plugins/replication/repl5_replica_hash.c243
-rw-r--r--ldap/servers/plugins/replication/repl5_replsupplier.c166
-rw-r--r--ldap/servers/plugins/replication/repl5_ruv.c2022
-rw-r--r--ldap/servers/plugins/replication/repl5_ruv.h88
-rw-r--r--ldap/servers/plugins/replication/repl5_schedule.c742
-rw-r--r--ldap/servers/plugins/replication/repl5_tot_protocol.c372
-rw-r--r--ldap/servers/plugins/replication/repl5_total.c869
-rw-r--r--ldap/servers/plugins/replication/repl5_updatedn_list.c243
-rw-r--r--ldap/servers/plugins/replication/repl_add.c30
-rw-r--r--ldap/servers/plugins/replication/repl_bind.c48
-rw-r--r--ldap/servers/plugins/replication/repl_compare.c34
-rw-r--r--ldap/servers/plugins/replication/repl_connext.c91
-rw-r--r--ldap/servers/plugins/replication/repl_controls.c337
-rw-r--r--ldap/servers/plugins/replication/repl_delete.c26
-rw-r--r--ldap/servers/plugins/replication/repl_entry.c38
-rw-r--r--ldap/servers/plugins/replication/repl_ext.c113
-rw-r--r--ldap/servers/plugins/replication/repl_extop.c1134
-rw-r--r--ldap/servers/plugins/replication/repl_globals.c108
-rw-r--r--ldap/servers/plugins/replication/repl_helper.c85
-rw-r--r--ldap/servers/plugins/replication/repl_helper.h69
-rw-r--r--ldap/servers/plugins/replication/repl_init.c312
-rw-r--r--ldap/servers/plugins/replication/repl_modify.c29
-rw-r--r--ldap/servers/plugins/replication/repl_modrdn.c28
-rw-r--r--ldap/servers/plugins/replication/repl_monitor.c58
-rw-r--r--ldap/servers/plugins/replication/repl_objset.c524
-rw-r--r--ldap/servers/plugins/replication/repl_objset.h37
-rw-r--r--ldap/servers/plugins/replication/repl_opext.c97
-rw-r--r--ldap/servers/plugins/replication/repl_ops.c180
-rw-r--r--ldap/servers/plugins/replication/repl_rootdse.c79
-rw-r--r--ldap/servers/plugins/replication/repl_search.c25
-rw-r--r--ldap/servers/plugins/replication/repl_shared.h132
-rw-r--r--ldap/servers/plugins/replication/replication.def16
-rw-r--r--ldap/servers/plugins/replication/replutil.c1073
-rw-r--r--ldap/servers/plugins/replication/tests/dnp_sim.c1033
-rw-r--r--ldap/servers/plugins/replication/tests/dnp_sim2.c972
-rw-r--r--ldap/servers/plugins/replication/tests/dnp_sim3.c1489
-rwxr-xr-xldap/servers/plugins/replication/tests/makesim58
-rw-r--r--ldap/servers/plugins/replication/urp.c1282
-rw-r--r--ldap/servers/plugins/replication/urp.h45
-rw-r--r--ldap/servers/plugins/replication/urp_glue.c235
-rw-r--r--ldap/servers/plugins/replication/urp_tombstone.c210
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**)&currentDir);
+
+ 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**)&currentDir);
+
+ 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, &current_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(&current_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",
+ &current_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(&current_purl);
+ DS_Sleep(PR_MillisecondsToInterval(100));
+ }
+
+ slapi_ch_free_string(&current_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,
+ &current_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(&current_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;
+}