From 676b659bff4763d7833ff6a8071a64ce42e6a1d7 Mon Sep 17 00:00:00 2001
From: "Thierry bordaz (tbordaz)" <tbordaz@redhat.com>
Date: Thu, 26 Feb 2015 13:48:57 +0100
Subject: [PATCH] Ticket 47936: Create a global lock to serialize write
 operations over several backends

Bug Description:
        Create a global lock that should be acquired when updating specified backends

Fix Description:
	It creates a config entry : cn=global backend lock,cn=config
	The entry defines the list of backends where a global lock
	should be acquired before write operation. The list is defined
	multi-valued attributes:
		backend-type
		backend-name
        Currently it implements a global lock over:
                ldbm database
                frontend-backend (cn=config)

	For example:
		# Global lock for all bdb backend + cn=config

		dn: cn=global backend lock,cn=config
		objectClass: top
		objectClass: extensibleobject
		cn: global backend lock
		backend-type: ldbm database
		backend-type: DSE

			or

		# Global lock for userRoot and ipaca

		dn: cn=global backend lock,cn=config
		objectClass: top
		objectClass: extensibleobject
		cn: global backend lock
		backend-name: userRoot
		backend-name: ipaca

			or

		# Global lock for cn=config + userRoot and changelogdb

		dn: cn=global backend lock,cn=config
		objectClass: top
		objectClass: extensibleobject
		cn: global backend lock
		backend-name: userRoot
		backend-name: changelogdb
		backend-type: DSE

https://fedorahosted.org/389/ticket/47936

Reviewed by: ?

Platforms tested: F20/F21

Flag Day: no

Doc impact: no
---
 ldap/servers/slapd/back-ldbm/dblayer.c |  10 +-
 ldap/servers/slapd/backend.c           | 192 +++++++++++++++++++++++++++++++++
 ldap/servers/slapd/dse.c               |  38 ++++++-
 ldap/servers/slapd/main.c              |   3 +-
 ldap/servers/slapd/proto-slap.h        |   4 +
 5 files changed, 243 insertions(+), 4 deletions(-)

diff --git a/ldap/servers/slapd/back-ldbm/dblayer.c b/ldap/servers/slapd/back-ldbm/dblayer.c
index ddf6bb0..a583c9a 100644
--- a/ldap/servers/slapd/back-ldbm/dblayer.c
+++ b/ldap/servers/slapd/back-ldbm/dblayer.c
@@ -769,7 +769,7 @@ int dblayer_terminate(struct ldbminfo *li)
     if (config_get_entryusn_global()) {
         slapi_counter_destroy(&li->li_global_usn_counter);
     }
-
+    
     return 0;
 }
 
@@ -3967,6 +3967,10 @@ void dblayer_lock_backend(backend *be)
 {
     ldbm_instance *inst;
 
+    if (global_backend_lock_requested(be)) {
+        global_backend_lock_lock();
+    }
+    
     PR_ASSERT(NULL != be);
     inst = (ldbm_instance *) be->be_instance_info;
     PR_ASSERT(NULL != inst);
@@ -3987,6 +3991,10 @@ void dblayer_unlock_backend(backend *be)
     if (NULL != inst->inst_db_mutex) {
         PR_ExitMonitor(inst->inst_db_mutex);
     }
+    
+    if (global_backend_lock_requested(be)) {
+        global_backend_lock_unlock();
+    }
 }
 
 
diff --git a/ldap/servers/slapd/backend.c b/ldap/servers/slapd/backend.c
index 22f41ee..ddf7598 100644
--- a/ldap/servers/slapd/backend.c
+++ b/ldap/servers/slapd/backend.c
@@ -40,9 +40,34 @@
 #  include <config.h>
 #endif
 
+
+
 /* backend.c - Slapi_Backend methods */
 
 #include "slap.h"
+#include "nspr.h"
+
+/*
+ * Global backend lock
+ * This lock is required if a betxn_post op plugin
+ *    - implement a own plugin lock (to protect its own plugin data)
+ *    - is doing internal operations (read/write) on others backends while holding its own lock
+ * 
+ * lock:   is called by the implemented backend (ldbm database, DSE..) before calling the BE_TXNPREOP  plugins
+ * unlock: is called by the implemented backend (ldbm database, DSE..) after  calling the BE_TXNPOSTOP plugins
+ * 
+ * It protects the requested backends during: BE_TXNPREOP/BE update/BE_TXNPOSTOP
+ */
+struct backend_global_lock_config {
+    /* those names must match be->be_name ("userroot", "frontend-internal",..)*/
+    char **backends_names;
+    
+    /* those types must match be->be_type ("ldbm database", "DSE",..) */
+    char **backends_types;
+    
+    /* Global backend lock*/
+    PRMonitor *global_db_mutex;
+} backend_global_lock_config = {NULL};
 
 void
 be_init( Slapi_Backend *be, const char *type, const char *name, int isprivate, int logchanges, int sizelimit, int timelimit )
@@ -146,6 +171,173 @@ be_done(Slapi_Backend *be)
         be->be_lock = NULL;
     }
 }
+#define GLOBAL_LOCK_ENTRY "cn=global backend lock,cn=config"
+#define GLOBAL_LOCK_BACKEND_TYPE "backend-type"
+#define GLOBAL_LOCK_BACKEND_NAME "backend-name"
+
+
+static int
+create_global_lock_entry() 
+{
+    /* DN part of this entry_string: no need to be optimized. */
+    char entry_string[1024];
+    Slapi_PBlock *pb;
+    Slapi_Entry *e;
+    int return_value;
+    int rc = 0;
+
+    /* Create cn=global backend lock,cn=config */
+    PR_snprintf(entry_string, sizeof (entry_string), "dn: %s\nobjectclass: top\nobjectclass: extensibleobject\ncn: global backend lock\n",
+            GLOBAL_LOCK_ENTRY);
+    e = slapi_str2entry(entry_string, 0);
+    pb = slapi_pblock_new();
+    slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */
+            plugin_get_default_component_id(), 0 /* flags */);
+    slapi_add_internal_pb(pb);
+    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+    if (return_value != LDAP_SUCCESS && return_value != LDAP_ALREADY_EXISTS) {
+        slapi_log_error(SLAPI_LOG_FATAL, NULL, "Warning: unable to "
+                "create global lock entry %s: %s\n", GLOBAL_LOCK_ENTRY,
+                ldap_err2string(return_value));
+        rc = -1;
+        slapi_entry_free(e); /* The entry was not consumed */
+        goto done;
+    }
+
+done:
+    if (pb) {
+        slapi_pblock_destroy(pb);
+        pb = NULL;
+    }
+    return rc;
+}
+
+
+static void
+load_global_lock_attr(Slapi_Entry *entry, char *attr_name, char ***backend_cfg, char *msg)
+{
+    Slapi_Attr *attr = NULL;
+    Slapi_Value *sval = NULL;
+    const struct berval *attrVal = NULL;
+    char **backend_array;
+    char *value;
+    int k, i;
+    int nbvalues;
+
+    slapi_entry_attr_find(entry, attr_name, &attr);
+    if (attr != NULL) {
+
+        slapi_attr_get_numvalues(attr, &nbvalues);
+        if (nbvalues == 0) {
+            *backend_cfg = NULL;
+        } else {
+            backend_array = (char **) slapi_ch_calloc(nbvalues + 1, sizeof (char *));
+            k = slapi_attr_first_value(attr, &sval);
+            i = 0;
+            /* For each (non empty) value of the backend type add it in backends_types */
+            while (k != -1) {
+                attrVal = slapi_value_get_berval(sval);
+                if (attrVal->bv_len) {
+                    /* copy the value in the backend_cfg array */
+                    value = (char *) slapi_ch_malloc(attrVal->bv_len + 1);
+                    memcpy(value, attrVal->bv_val, attrVal->bv_len);
+                    value[attrVal->bv_len] = '\0';
+                    backend_array[i] = value;
+#if 1
+                     slapi_log_error(SLAPI_LOG_CONFIG, NULL, "Global backend lock applies on %s: %s\n", 
+                             msg ? msg : "", value);
+#endif
+                    i++;
+                }
+                k = slapi_attr_next_value(attr, k, &sval);
+            }
+	    *backend_cfg = backend_array;
+        }
+    }
+}
+
+static int
+load_global_lock_entry() 
+{
+    Slapi_DN sdn;
+    Slapi_Entry *entry = NULL;
+    Slapi_Attr *attr = NULL;
+    Slapi_Value *sval = NULL;
+    const struct berval *attrVal = NULL;
+    char *value;
+    int k, i;
+    int nbvalues;
+
+    /* a priori there is no global lock configuration */
+    backend_global_lock_config.backends_types = NULL;
+    backend_global_lock_config.backends_names = NULL;
+    
+    /* Load the global lock config entry  */
+    slapi_sdn_init_dn_byval(&sdn, GLOBAL_LOCK_ENTRY);
+    if (slapi_search_internal_get_entry(&sdn, NULL, &entry, plugin_get_default_component_id()) == LDAP_SUCCESS) {
+        
+        /* First load the configuration related to backend type */
+        load_global_lock_attr(entry, GLOBAL_LOCK_BACKEND_TYPE, &backend_global_lock_config.backends_types, "backend type");
+        
+        /* First load the configuration related to backend names */
+        load_global_lock_attr(entry, GLOBAL_LOCK_BACKEND_NAME, &backend_global_lock_config.backends_names, "backend named");
+        
+        slapi_entry_free(entry);
+    }
+    slapi_sdn_done(&sdn);
+}
+
+void
+global_backend_lock_init()
+{
+    /* Create the global backend entry in dse.ldif (if it does not already exist) */
+    create_global_lock_entry();
+    
+    /* load its config names/types of backends where global lock applies */
+    load_global_lock_entry();
+    
+    backend_global_lock_config.global_db_mutex = PR_NewMonitor();
+    
+}
+PRBool
+global_backend_lock_requested(Slapi_Backend *be)
+{
+    int i;
+
+    if (be == NULL)
+        return PR_FALSE;
+
+    if (be->be_type) {
+        for (i = 0; backend_global_lock_config.backends_types && backend_global_lock_config.backends_types[i]; i++) {
+            if (PL_strcasecmp(backend_global_lock_config.backends_types[i], be->be_type) == 0) {
+                return PR_TRUE;
+            }
+        }
+    }
+
+    if (be->be_name) {
+        for (i = 0; backend_global_lock_config.backends_names && backend_global_lock_config.backends_names[i]; i++) {
+            if (PL_strcasecmp(backend_global_lock_config.backends_names[i], be->be_name) == 0) {
+                return PR_TRUE;
+            }
+        }
+    }
+    return PR_FALSE;
+}
+void
+global_backend_lock_lock() 
+{
+    if (backend_global_lock_config.global_db_mutex) {
+        PR_EnterMonitor(backend_global_lock_config.global_db_mutex);
+    }
+}
+
+void
+global_backend_lock_unlock() {
+    if (backend_global_lock_config.global_db_mutex) {
+        PR_ExitMonitor(backend_global_lock_config.global_db_mutex);
+    }
+}
 
 void
 slapi_be_delete_onexit (Slapi_Backend *be)
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
index b9ffc5f..f53eafb 100644
--- a/ldap/servers/slapd/dse.c
+++ b/ldap/servers/slapd/dse.c
@@ -1828,6 +1828,8 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
     int need_be_postop = 0;
     int plugin_started = 0;
     int internal_op = 0;
+    Slapi_Backend *be;
+    PRBool global_lock_owned = PR_FALSE;
 
     PR_ASSERT(pb);
     if (slapi_pblock_get( pb, SLAPI_PLUGIN_PRIVATE, &pdse ) < 0 ||
@@ -1837,6 +1839,7 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
         returncode = LDAP_OPERATIONS_ERROR;
         goto done;
     }
+    slapi_pblock_get( pb, SLAPI_BACKEND, &be);
 	
     slapi_pblock_get(pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &dont_write_file);
     if ( !dont_write_file && dse_check_for_readonly_error(pb,pdse)) {
@@ -1871,6 +1874,12 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
     /* Modify a copy of the entry*/
     ecc = slapi_entry_dup( ec );
     err = entry_apply_mods( ecc, mods );
+    
+    /* Possibly acquire the global backend lock */
+    if (global_backend_lock_requested(be)) {
+        global_backend_lock_lock();
+        global_lock_owned = PR_TRUE;
+    }
 
     /* XXXmcs: should we expand objectclass values here?? */
     /* give the dse callbacks the first crack at the modify */
@@ -2069,7 +2078,9 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
             }
         }
     }
-
+    if (global_lock_owned) {
+        global_backend_lock_unlock();
+    }
     slapi_send_ldap_result( pb, returncode, NULL, returntext[0] ? returntext : NULL, 0, NULL );
 
     return dse_modify_return(retval, ec, ecc);
@@ -2224,6 +2235,8 @@ dse_add(Slapi_PBlock *pb) /* JCM There should only be one exit point from this f
     Slapi_DN *sdn = NULL;
     Slapi_DN parent;
     int need_be_postop = 0;
+    Slapi_Backend *be;
+    PRBool global_lock_owned = PR_FALSE;
 
     /*
      * Get the database, the dn and the entry to add
@@ -2234,6 +2247,7 @@ dse_add(Slapi_PBlock *pb) /* JCM There should only be one exit point from this f
         rc = LDAP_OPERATIONS_ERROR;
         goto done;
     }
+    slapi_pblock_get( pb, SLAPI_BACKEND, &be);
 
     slapi_pblock_get(pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &dont_write_file);
     if ( !dont_write_file && dse_check_for_readonly_error(pb,pdse)) {
@@ -2345,6 +2359,12 @@ dse_add(Slapi_PBlock *pb) /* JCM There should only be one exit point from this f
         goto done;
     }
 
+    /* Possibly acquire the global backend lock */
+    if (global_backend_lock_requested(be)) {
+        global_backend_lock_lock();
+        global_lock_owned = PR_TRUE;
+    }
+
     if(dse_call_callback(pdse, pb, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, e,
                          NULL, &returncode, returntext)!=SLAPI_DSE_CALLBACK_OK) {
         if (!returncode) {
@@ -2424,7 +2444,9 @@ dse_add(Slapi_PBlock *pb) /* JCM There should only be one exit point from this f
             slapi_pblock_get(pb, SLAPI_RESULT_CODE, &returncode);
         }
     }
-
+    if (global_lock_owned) {
+        global_backend_lock_unlock();
+    }
     slapi_send_ldap_result(pb, returncode, NULL, returntext[0] ? returntext : NULL, 0, NULL );
     return dse_add_return(rc, e);
 }
@@ -2456,6 +2478,8 @@ dse_delete(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
     Slapi_Entry *ec = NULL; /* copy of entry to delete */
     Slapi_Entry *orig_entry = NULL;
     int need_be_postop = 0;
+    Slapi_Backend *be;
+    PRBool global_lock_owned = PR_FALSE;
 
     /*
      * Get the database and the dn
@@ -2466,6 +2490,7 @@ dse_delete(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
         returncode = LDAP_OPERATIONS_ERROR;
         goto done;
     }
+    slapi_pblock_get( pb, SLAPI_BACKEND, &be);
 
     slapi_pblock_get(pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, &dont_write_file);
     if ( !dont_write_file && dse_check_for_readonly_error(pb,pdse)) {
@@ -2500,6 +2525,12 @@ dse_delete(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
         goto done;
     }
 
+    /* Possibly acquire the global backend lock */
+    if (global_backend_lock_requested(be)) {
+        global_backend_lock_lock();
+        global_lock_owned = PR_TRUE;
+    }
+
     if(dse_call_callback(pdse, pb, SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, ec, NULL, &returncode,returntext)==SLAPI_DSE_CALLBACK_OK) {
         slapi_pblock_set(pb, SLAPI_DELETE_BEPREOP_ENTRY, ec);
         slapi_pblock_set(pb, SLAPI_RESULT_CODE, &returncode);
@@ -2552,6 +2583,9 @@ done:
             slapi_pblock_get(pb, SLAPI_RESULT_CODE, &returncode);
         }
     }
+    if (global_lock_owned) {
+        global_backend_lock_unlock();
+    }
     if (returncode && !returntext[0]) {
         char *ldap_result_message = NULL;
         slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message);
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
index 6bad2a0..a5fafb1 100644
--- a/ldap/servers/slapd/main.c
+++ b/ldap/servers/slapd/main.c
@@ -841,7 +841,7 @@ main( int argc, char **argv)
 			exit (1);
 		}
 		entry_computed_attr_init();
-
+        
 		if (0 == setup_internal_backends(slapdFrontendConfig->configdir)) {
 			slapi_log_error(SLAPI_LOG_FATAL, "startup",
 							"The configuration files in directory %s could not be read or were not found.  Please refer to the error log or output for more information.\n",
@@ -1057,6 +1057,7 @@ main( int argc, char **argv)
 	/* initialize the normalized DN cache */
 	ndn_cache_init();
 
+            global_backend_lock_init();
 	/*
 	 * Detach ourselves from the terminal (unless running in debug mode).
 	 * We must detach before we start any threads since detach forks() on
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 35b45e3..e4d8016 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -213,6 +213,10 @@ void be_addsuffix(Slapi_Backend *be,const Slapi_DN *suffix);
 Slapi_DN *be_getconfigdn(Slapi_Backend *be,Slapi_DN *dn);
 Slapi_DN *be_getmonitordn(Slapi_Backend *be,Slapi_DN *dn);
 int be_writeconfig (Slapi_Backend *be);
+void global_backend_lock_init();
+PRBool global_backend_lock_requested(Slapi_Backend *be);
+void global_backend_lock_lock();
+void global_backend_lock_unlock();
 
 /*
  * backend_manager.c
-- 
1.7.11.7

