ldb: Ensure we can open a new LDB after a fork()
[samba.git] / lib / ldb / tests / ldb_mod_op_test.c
index 6357f83dd024a4f363782f9b24af0361494afce3..67ac024db86be8a6249a15f8636352b33cc9dbfc 100644 (file)
@@ -114,9 +114,104 @@ static void test_connect(void **state)
        assert_int_equal(ret, 0);
 }
 
+static struct ldb_message *get_test_ldb_message(TALLOC_CTX *mem_ctx,
+                                               struct ldb_context *ldb)
+{
+       struct ldb_message *msg = ldb_msg_new(mem_ctx);
+       int ret;
+       assert_non_null(msg);
+
+       msg->dn = ldb_dn_new(msg, ldb, "dc=samba,dc=org");
+       assert_non_null(msg->dn);
+       ret = ldb_msg_add_string(msg, "public", "key");
+       assert_int_equal(ret, LDB_SUCCESS);
+       ret = ldb_msg_add_string(msg, "supersecret", "password");
+       assert_int_equal(ret, LDB_SUCCESS);
+       ret = ldb_msg_add_string(msg, "binary", "\xff\xff\0");
+       assert_int_equal(ret, LDB_SUCCESS);
+       return msg;
+}
+
+static void test_ldif_message(void **state)
+{
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       char *got_ldif;
+       const char *expected_ldif =
+               "dn: dc=samba,dc=org\n"
+               "changetype: add\n"
+               "public: key\n"
+               "supersecret: password\n"
+               "binary:: //8=\n"
+               "\n";
+       
+       struct ldb_message *msg = get_test_ldb_message(test_ctx,
+                                                      test_ctx->ldb);
+
+       got_ldif = ldb_ldif_message_string(test_ctx->ldb,
+                                          test_ctx,
+                                          LDB_CHANGETYPE_ADD,
+                                          msg);
+       assert_string_equal(got_ldif, expected_ldif);
+       TALLOC_FREE(got_ldif);
+}
+
+static void test_ldif_message_redacted(void **state)
+{
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       int ret;
+       char *got_ldif;
+       const char *expected_ldif =
+               "dn: dc=samba,dc=org\n"
+               "changetype: add\n"
+               "public: key\n"
+               "# supersecret::: REDACTED SECRET ATTRIBUTE\n"
+               "binary:: //8=\n"
+               "\n";
+
+       const char *secret_attrs[] = {
+               "supersecret",
+               NULL
+       };
+       
+       struct ldb_message *msg = ldb_msg_new(test_ctx);
+
+       ldb_set_opaque(test_ctx->ldb,
+                      LDB_SECRET_ATTRIBUTE_LIST_OPAQUE,
+                      secret_attrs);
+       
+       assert_non_null(msg);
+
+       msg->dn = ldb_dn_new(msg, test_ctx->ldb, "dc=samba,dc=org");
+       ret = ldb_msg_add_string(msg, "public", "key");
+       assert_int_equal(ret, LDB_SUCCESS);
+       ret = ldb_msg_add_string(msg, "supersecret", "password");
+       assert_int_equal(ret, LDB_SUCCESS);
+       ret = ldb_msg_add_string(msg, "binary", "\xff\xff\0");
+       assert_int_equal(ret, LDB_SUCCESS);
+       got_ldif = ldb_ldif_message_redacted_string(test_ctx->ldb,
+                                                   test_ctx,
+                                                   LDB_CHANGETYPE_ADD,
+                                                   msg);
+       assert_string_equal(got_ldif, expected_ldif);
+       TALLOC_FREE(got_ldif);
+       assert_int_equal(ret, 0);
+}
+
 static int ldbtest_setup(void **state)
 {
        struct ldbtest_ctx *test_ctx;
+       struct ldb_ldif *ldif;
+#ifdef GUID_IDX
+       const char *index_ldif =                \
+               "dn: @INDEXLIST\n"
+               "@IDXGUID: objectUUID\n"
+               "@IDX_DN_GUID: GUID\n"
+               "\n";
+#else
+       const char *index_ldif = "\n";
+#endif
        int ret;
 
        ldbtest_noconn_setup((void **) &test_ctx);
@@ -124,6 +219,10 @@ static int ldbtest_setup(void **state)
        ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
        assert_int_equal(ret, 0);
 
+       while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
+               ret = ldb_add(test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
        *state = test_ctx;
        return 0;
 }
@@ -156,6 +255,9 @@ static void test_ldb_add(void **state)
        ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
        assert_int_equal(ret, 0);
 
+       ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef");
+       assert_int_equal(ret, 0);
+
        ret = ldb_add(test_ctx->ldb, msg);
        assert_int_equal(ret, 0);
 
@@ -194,6 +296,9 @@ static void test_ldb_search(void **state)
        ret = ldb_msg_add_string(msg, "cn", "test_cn_val1");
        assert_int_equal(ret, 0);
 
+       ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcde1");
+       assert_int_equal(ret, 0);
+
        ret = ldb_add(test_ctx->ldb, msg);
        assert_int_equal(ret, 0);
 
@@ -209,6 +314,9 @@ static void test_ldb_search(void **state)
        ret = ldb_msg_add_string(msg, "cn", "test_cn_val2");
        assert_int_equal(ret, 0);
 
+       ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcde2");
+       assert_int_equal(ret, 0);
+
        ret = ldb_add(test_ctx->ldb, msg);
        assert_int_equal(ret, 0);
 
@@ -305,7 +413,8 @@ static void assert_dn_doesnt_exist(struct ldbtest_ctx *test_ctx,
 
 static void add_dn_with_cn(struct ldbtest_ctx *test_ctx,
                           struct ldb_dn *dn,
-                          const char *cn_value)
+                          const char *cn_value,
+                          const char *uuid_value)
 {
        int ret;
        TALLOC_CTX *tmp_ctx;
@@ -324,6 +433,9 @@ static void add_dn_with_cn(struct ldbtest_ctx *test_ctx,
        ret = ldb_msg_add_string(msg, "cn", cn_value);
        assert_int_equal(ret, LDB_SUCCESS);
 
+       ret = ldb_msg_add_string(msg, "objectUUID", uuid_value);
+       assert_int_equal(ret, 0);
+
        ret = ldb_add(test_ctx->ldb, msg);
        assert_int_equal(ret, LDB_SUCCESS);
 
@@ -343,7 +455,9 @@ static void test_ldb_del(void **state)
        dn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "%s", basedn);
        assert_non_null(dn);
 
-       add_dn_with_cn(test_ctx, dn, "test_del_cn_val");
+       add_dn_with_cn(test_ctx, dn,
+                      "test_del_cn_val",
+                      "0123456789abcdef");
 
        ret = ldb_delete(test_ctx->ldb, dn);
        assert_int_equal(ret, LDB_SUCCESS);
@@ -467,7 +581,8 @@ static void test_ldb_build_search_req(void **state)
 
 static void add_keyval(struct ldbtest_ctx *test_ctx,
                       const char *key,
-                      const char *val)
+                      const char *val,
+                      const char *uuid)
 {
        int ret;
        struct ldb_message *msg;
@@ -481,6 +596,9 @@ static void add_keyval(struct ldbtest_ctx *test_ctx,
        ret = ldb_msg_add_string(msg, key, val);
        assert_int_equal(ret, 0);
 
+       ret = ldb_msg_add_string(msg, "objectUUID", uuid);
+       assert_int_equal(ret, 0);
+
        ret = ldb_add(test_ctx->ldb, msg);
        assert_int_equal(ret, 0);
 
@@ -516,7 +634,8 @@ static void test_transactions(void **state)
        ret = ldb_transaction_start(test_ctx->ldb);
        assert_int_equal(ret, 0);
 
-       add_keyval(test_ctx, "vegetable", "carrot");
+       add_keyval(test_ctx, "vegetable", "carrot",
+                  "0123456789abcde0");
 
        /* commit lev-0 transaction */
        ret = ldb_transaction_commit(test_ctx->ldb);
@@ -526,7 +645,8 @@ static void test_transactions(void **state)
        ret = ldb_transaction_start(test_ctx->ldb);
        assert_int_equal(ret, 0);
 
-       add_keyval(test_ctx, "fruit", "apple");
+       add_keyval(test_ctx, "fruit", "apple",
+                  "0123456789abcde1");
 
        /* abort lev-1 nested transaction */
        ret = ldb_transaction_cancel(test_ctx->ldb);
@@ -541,6 +661,48 @@ static void test_transactions(void **state)
        assert_int_equal(res->count, 0);
 }
 
+static void test_nested_transactions(void **state)
+{
+       int ret;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                       struct ldbtest_ctx);
+       struct ldb_result *res;
+
+       /* start lev-0 transaction */
+       ret = ldb_transaction_start(test_ctx->ldb);
+       assert_int_equal(ret, 0);
+
+       add_keyval(test_ctx, "vegetable", "carrot",
+                  "0123456789abcde0");
+
+
+       /* start another lev-1 nested transaction */
+       ret = ldb_transaction_start(test_ctx->ldb);
+       assert_int_equal(ret, 0);
+
+       add_keyval(test_ctx, "fruit", "apple",
+                  "0123456789abcde1");
+
+       /* abort lev-1 nested transaction */
+       ret = ldb_transaction_cancel(test_ctx->ldb);
+       assert_int_equal(ret, 0);
+
+       /* commit lev-0 transaction */
+       ret = ldb_transaction_commit(test_ctx->ldb);
+       assert_int_equal(ret, 0);
+
+       res = get_keyval(test_ctx, "vegetable", "carrot");
+       assert_non_null(res);
+       assert_int_equal(res->count, 1);
+
+       /* This documents the current ldb behaviour,  i.e. nested
+        * transactions are not supported.  And the cancellation of the nested
+        * transaction has no effect.
+        */
+       res = get_keyval(test_ctx, "fruit", "apple");
+       assert_non_null(res);
+       assert_int_equal(res->count, 1);
+}
 struct ldb_mod_test_ctx {
        struct ldbtest_ctx *ldb_test_ctx;
        const char *entry_dn;
@@ -700,6 +862,7 @@ static int ldb_modify_test_setup(void **state)
        struct ldb_mod_test_ctx *mod_test_ctx;
        struct keyval kvs[] = {
                { "cn", "test_mod_cn" },
+               { "objectUUID", "0123456789abcdef"},
                { NULL, NULL },
        };
 
@@ -1004,6 +1167,7 @@ static int ldb_search_test_setup(void **state)
                { "cn", "test_search_cn2" },
                { "uid", "test_search_uid" },
                { "uid", "test_search_uid2" },
+               { "objectUUID", "0123456789abcde0"},
                { NULL, NULL },
        };
        struct keyval kvs2[] = {
@@ -1011,6 +1175,7 @@ static int ldb_search_test_setup(void **state)
                { "cn", "test_search_2_cn2" },
                { "uid", "test_search_2_uid" },
                { "uid", "test_search_2_uid2" },
+               { "objectUUID", "0123456789abcde1"},
                { NULL, NULL },
        };
 
@@ -1285,7 +1450,7 @@ static void test_search_match_basedn(void **state)
  *  - (2) the ldb_transaction_commit() is called.
  *        This returns LDB_ERR_BUSY if the deadlock is detected
  *
- * With ldb 1.1.29 and tdb 1.3.12 we avoid this only due to a missing
+ * With ldb 1.1.31 and tdb 1.3.12 we avoid this only due to a missing
  * lock call in ltdb_search() due to a refcounting bug in
  * ltdb_lock_read()
  */
@@ -1327,7 +1492,7 @@ static int test_ldb_search_against_transaction_callback2(struct ldb_request *req
  * we take any locks in the tdb_traverse_read() handler.
  *
  * In tdb 1.3.12 tdb_traverse_read() take the read transaction lock
- * however in ldb 1.1.29 ltdb_search() forgets to take the all-record
+ * however in ldb 1.1.31 ltdb_search() forgets to take the all-record
  * lock (except the very first time) due to a ref-counting bug.
  *
  */
@@ -1359,6 +1524,7 @@ static int test_ldb_search_against_transaction_callback1(struct ldb_request *req
                struct ldb_message *msg;
                TALLOC_FREE(ctx->test_ctx->ldb);
                TALLOC_FREE(ctx->test_ctx->ev);
+               close(pipes[0]);
                ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
                if (ctx->test_ctx->ev == NULL) {
                        exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1407,6 +1573,12 @@ static int test_ldb_search_against_transaction_callback1(struct ldb_request *req
                        exit(LDB_ERR_OPERATIONS_ERROR);
                }
 
+               ret = ldb_msg_add_string(msg, "objectUUID",
+                                        "0123456789abcdef");
+               if (ret != 0) {
+                       exit(ret);
+               }
+
                ret = ldb_add(ctx->test_ctx->ldb, msg);
                if (ret != 0) {
                        exit(ret);
@@ -1415,7 +1587,7 @@ static int test_ldb_search_against_transaction_callback1(struct ldb_request *req
                ret = ldb_transaction_commit(ctx->test_ctx->ldb);
                exit(ret);
        }
-
+       close(pipes[1]);
        ret = read(pipes[0], buf, 2);
        assert_int_equal(ret, 2);
 
@@ -1589,6 +1761,7 @@ static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
                struct ldb_dn *dn, *new_dn;
                TALLOC_FREE(ctx->test_ctx->ldb);
                TALLOC_FREE(ctx->test_ctx->ev);
+               close(pipes[0]);
                ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
                if (ctx->test_ctx->ev == NULL) {
                        exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1655,6 +1828,7 @@ static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
                struct ldb_message_element *el;
                TALLOC_FREE(ctx->test_ctx->ldb);
                TALLOC_FREE(ctx->test_ctx->ev);
+               close(pipes[0]);
                ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
                if (ctx->test_ctx->ev == NULL) {
                        exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1730,6 +1904,7 @@ static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
         * sending the "GO" as it is blocked at ldb_transaction_start().
         */
 
+       close(pipes[1]);
        ret = read(pipes[0], buf, 2);
        assert_int_equal(ret, 2);
 
@@ -1769,10 +1944,13 @@ static void test_ldb_modify_during_search(void **state, bool add_index,
 
                ret = ldb_msg_add_string(msg, "@IDXATTR", "cn");
                assert_int_equal(ret, LDB_SUCCESS);
-
                ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb,
                              msg);
-
+               if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+                       msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+                       ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb,
+                                        msg);
+               }
                assert_int_equal(ret, LDB_SUCCESS);
        }
 
@@ -1829,22 +2007,22 @@ static void test_ldb_modify_during_search(void **state, bool add_index,
 
 static void test_ldb_modify_during_indexed_search(void **state)
 {
-       return test_ldb_modify_during_search(state, true, false);
+       test_ldb_modify_during_search(state, true, false);
 }
 
 static void test_ldb_modify_during_unindexed_search(void **state)
 {
-       return test_ldb_modify_during_search(state, false, false);
+       test_ldb_modify_during_search(state, false, false);
 }
 
 static void test_ldb_rename_during_indexed_search(void **state)
 {
-       return test_ldb_modify_during_search(state, true, true);
+       test_ldb_modify_during_search(state, true, true);
 }
 
 static void test_ldb_rename_during_unindexed_search(void **state)
 {
-       return test_ldb_modify_during_search(state, false, true);
+       test_ldb_modify_during_search(state, false, true);
 }
 
 /*
@@ -1901,6 +2079,7 @@ static int test_ldb_modify_during_whole_search_callback1(struct ldb_request *req
                struct ldb_message_element *el;
                TALLOC_FREE(ctx->test_ctx->ldb);
                TALLOC_FREE(ctx->test_ctx->ev);
+               close(pipes[0]);
                ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
                if (ctx->test_ctx->ev == NULL) {
                        exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1963,6 +2142,7 @@ static int test_ldb_modify_during_whole_search_callback1(struct ldb_request *req
                exit(ret);
        }
 
+       close(pipes[1]);
        ret = read(pipes[0], buf, 2);
        assert_int_equal(ret, 2);
 
@@ -2076,99 +2256,325 @@ static void test_ldb_modify_during_whole_search(void **state)
        assert_int_equal(res2->count, 1);
 }
 
-static int ldb_case_test_setup(void **state)
-{
-       int ret;
-       struct ldb_ldif *ldif;
-       struct ldbtest_ctx *ldb_test_ctx;
-       const char *attrs_ldif =  \
-               "dn: @ATTRIBUTES\n"
-               "cn: CASE_INSENSITIVE\n"
-               "\n";
-       struct keyval kvs[] = {
-               { "cn", "CaseInsensitiveValue" },
-               { "uid", "CaseSensitiveValue" },
-               { NULL, NULL },
-       };
+/*
+ * This test is also complex.
+ *
+ * The purpose is to test if a modify can occur during an ldb_search()
+ * before the request is destroyed with TALLOC_FREE()
+ *
+ * This would be a failure if in process
+ * (1) and (2):
+ *  - (1) ldb_search() starts and waits
+ *  - (2) an entry in the DB is allowed to change before the ldb_wait() is called
+ *  - (1) the original process can see the modification before the TALLOC_FREE()
+ * also we check that
+ *  - (1) the original process can see the modification after the TALLOC_FREE()
+ *
+ */
 
+/*
+ * This purpose of this callback is to trigger a write in
+ * the child process before the ldb_wait() is called
+ *
+ * In ldb 1.1.31 ldb_search() omitted to take a all-record
+ * lock for the full duration of the search and callbacks
+ *
+ * We assume that if the write will proceed, it will proceed in a 3
+ * second window after the function is called.
+ */
 
-       ldbtest_setup((void **) &ldb_test_ctx);
+static int test_ldb_modify_before_ldb_wait_callback1(struct ldb_request *req,
+                                                    struct ldb_reply *ares)
+{
+       switch (ares->type) {
+       case LDB_REPLY_ENTRY:
+       case LDB_REPLY_REFERRAL:
+               return LDB_SUCCESS;
 
-       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) {
-               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
-               assert_int_equal(ret, LDB_SUCCESS);
+       case LDB_REPLY_DONE:
+               break;
        }
 
-       ldb_test_add_data(ldb_test_ctx,
-                         ldb_test_ctx,
-                         "cn=CaseInsensitiveValue",
-                         kvs);
-
-       *state = ldb_test_ctx;
-       return 0;
+       return ldb_request_done(req, LDB_SUCCESS);
 }
 
-static int ldb_case_test_teardown(void **state)
+static void test_ldb_modify_before_ldb_wait(void **state)
 {
+       struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+                       struct search_test_ctx);
        int ret;
-       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
-                       struct ldbtest_ctx);
+       struct ldb_request *req;
+       pid_t pid;
+       int wstatus;
+       struct ldb_dn *search_dn;
+       struct ldb_dn *basedn;
+       struct ldb_result *res2;
+       int pipes[2];
+       char buf[2];
+       pid_t child_pid;
+       unsigned res_count;
 
-       struct ldb_dn *del_dn;
+       search_dn = ldb_dn_new_fmt(search_test_ctx,
+                                  search_test_ctx->ldb_test_ctx->ldb,
+                                  "cn=test_search_cn,"
+                                  "dc=search_test_entry");
+       assert_non_null(search_dn);
 
-       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
-                               ldb_test_ctx->ldb,
-                               "@ATTRIBUTES");
-       assert_non_null(del_dn);
+       basedn = ldb_dn_new_fmt(search_test_ctx,
+                               search_test_ctx->ldb_test_ctx->ldb,
+                               "%s",
+                               search_test_ctx->base_dn);
+       assert_non_null(basedn);
 
-       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
-       assert_int_equal(ret, LDB_SUCCESS);
+       /*
+        * The search just needs to call DONE, we don't care about the
+        * contents of the search for this test
+        */
+       ret = ldb_build_search_req(&req,
+                                  search_test_ctx->ldb_test_ctx->ldb,
+                                  search_test_ctx,
+                                  basedn,
+                                  LDB_SCOPE_SUBTREE,
+                                  "(&(!(filterAttr=*))"
+                                  "(cn=test_search_cn))",
+                                  NULL,
+                                  NULL,
+                                  NULL,
+                                  test_ldb_modify_before_ldb_wait_callback1,
+                                  NULL);
+       assert_int_equal(ret, 0);
+       ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
 
-       assert_dn_doesnt_exist(ldb_test_ctx,
-                              "@ATTRIBUTES");
+       ret = pipe(pipes);
+       assert_int_equal(ret, 0);
 
-       ldb_test_remove_data(ldb_test_ctx, ldb_test_ctx,
-                            "cn=CaseInsensitiveValue");
+       child_pid = fork();
+       if (child_pid == 0) {
+               TALLOC_CTX *tmp_ctx = NULL;
+               struct ldb_message *msg;
+               struct ldb_message_element *el;
+               TALLOC_FREE(search_test_ctx->ldb_test_ctx->ldb);
+               TALLOC_FREE(search_test_ctx->ldb_test_ctx->ev);
+               close(pipes[0]);
+               search_test_ctx->ldb_test_ctx->ev = tevent_context_init(search_test_ctx->ldb_test_ctx);
+               if (search_test_ctx->ldb_test_ctx->ev == NULL) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
 
-       ldbtest_teardown((void **) &ldb_test_ctx);
-       return 0;
-}
+               search_test_ctx->ldb_test_ctx->ldb = ldb_init(search_test_ctx->ldb_test_ctx,
+                                            search_test_ctx->ldb_test_ctx->ev);
+               if (search_test_ctx->ldb_test_ctx->ldb == NULL) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
 
-static void test_ldb_attrs_case_insensitive(void **state)
-{
-       int cnt;
-       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
-                       struct ldbtest_ctx);
+               ret = ldb_connect(search_test_ctx->ldb_test_ctx->ldb,
+                                 search_test_ctx->ldb_test_ctx->dbpath, 0, NULL);
+               if (ret != LDB_SUCCESS) {
+                       exit(ret);
+               }
 
-       /* cn matches exact case */
-       cnt = sub_search_count(ldb_test_ctx, "", "cn=CaseInsensitiveValue");
-       assert_int_equal(cnt, 1);
+               tmp_ctx = talloc_new(search_test_ctx->ldb_test_ctx);
+               if (tmp_ctx == NULL) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
 
-       /* cn matches lower case */
-       cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
-       assert_int_equal(cnt, 1);
+               msg = ldb_msg_new(tmp_ctx);
+               if (msg == NULL) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
 
-       /* uid matches exact case */
-       cnt = sub_search_count(ldb_test_ctx, "", "uid=CaseSensitiveValue");
-       assert_int_equal(cnt, 1);
+               /*
+                * We must re-create this DN from a string to ensure
+                * it does not reference the now-gone LDB context of
+                * the parent
+                */
+               msg->dn = ldb_dn_new_fmt(search_test_ctx,
+                                        search_test_ctx->ldb_test_ctx->ldb,
+                                        "cn=test_search_cn,"
+                                        "dc=search_test_entry");
 
-       /* uid does not match lower case */
-       cnt = sub_search_count(ldb_test_ctx, "", "uid=casesensitivevalue");
-       assert_int_equal(cnt, 0);
-}
+               if (msg->dn == NULL) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
 
-static struct ldb_schema_attribute cn_attr_1;
-static struct ldb_schema_attribute cn_attr_2;
-static struct ldb_schema_attribute default_attr;
+               ret = ldb_msg_add_string(msg, "filterAttr", "TRUE");
+               if (ret != 0) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+               el = ldb_msg_find_element(msg, "filterAttr");
+               if (el == NULL) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+               el->flags = LDB_FLAG_MOD_REPLACE;
 
-/*
-  override the name to attribute handler function
- */
-static const struct ldb_schema_attribute *ldb_test_attribute_handler_override(struct ldb_context *ldb,
-                                                                             void *private_data,
-                                                                             const char *name)
-{
-       if (private_data != NULL && ldb_attr_cmp(name, "cn") == 0) {
+               ret = ldb_transaction_start(search_test_ctx->ldb_test_ctx->ldb);
+               if (ret != 0) {
+                       exit(ret);
+               }
+
+               if (write(pipes[1], "GO", 2) != 2) {
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+
+               ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb, msg);
+               if (ret != 0) {
+                       exit(ret);
+               }
+
+               ret = ldb_transaction_commit(search_test_ctx->ldb_test_ctx->ldb);
+               exit(ret);
+       }
+       close(pipes[1]);
+
+       ret = read(pipes[0], buf, 2);
+       assert_int_equal(ret, 2);
+
+       sleep(3);
+
+       /*
+        * If writes are not blocked until after the (never called) ldb_wait(), we
+        * will be able to successfully search for this modification
+        * here
+        */
+
+       ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb, search_test_ctx,
+                        &res2, search_dn, LDB_SCOPE_BASE, NULL,
+                        "filterAttr=TRUE");
+
+       /*
+        * We avoid making assertions before TALLOC_FREE()ing the request,
+        * lest the assert fail and mess with the clean-up because we still
+        * have locks.
+        */
+       res_count = res2->count;
+       TALLOC_FREE(req);
+
+       /* We should not have got the result */
+       assert_int_equal(res_count, 0);
+       assert_int_equal(ret, 0);
+
+       pid = waitpid(child_pid, &wstatus, 0);
+       assert_int_equal(pid, child_pid);
+
+       assert_true(WIFEXITED(wstatus));
+
+       assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+       /*
+        * If writes are blocked until after the search request was freed, we
+        * will be able to successfully search for this modification
+        * now
+        */
+
+       search_dn = ldb_dn_new_fmt(search_test_ctx,
+                                  search_test_ctx->ldb_test_ctx->ldb,
+                                  "cn=test_search_cn,"
+                                  "dc=search_test_entry");
+
+       ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb,
+                        search_test_ctx,
+                        &res2, search_dn, LDB_SCOPE_BASE, NULL,
+                        "filterAttr=TRUE");
+       assert_int_equal(ret, 0);
+
+       /* We got the result */
+       assert_int_equal(res2->count, 1);
+}
+
+static int ldb_case_test_setup(void **state)
+{
+       int ret;
+       struct ldb_ldif *ldif;
+       struct ldbtest_ctx *ldb_test_ctx;
+       const char *attrs_ldif =  \
+               "dn: @ATTRIBUTES\n"
+               "cn: CASE_INSENSITIVE\n"
+               "\n";
+       struct keyval kvs[] = {
+               { "cn", "CaseInsensitiveValue" },
+               { "uid", "CaseSensitiveValue" },
+               { "objectUUID", "0123456789abcdef" },
+               { NULL, NULL },
+       };
+
+
+       ldbtest_setup((void **) &ldb_test_ctx);
+
+       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) {
+               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       ldb_test_add_data(ldb_test_ctx,
+                         ldb_test_ctx,
+                         "cn=CaseInsensitiveValue",
+                         kvs);
+
+       *state = ldb_test_ctx;
+       return 0;
+}
+
+static int ldb_case_test_teardown(void **state)
+{
+       int ret;
+       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+                       struct ldbtest_ctx);
+
+       struct ldb_dn *del_dn;
+
+       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
+                               ldb_test_ctx->ldb,
+                               "@ATTRIBUTES");
+       assert_non_null(del_dn);
+
+       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       assert_dn_doesnt_exist(ldb_test_ctx,
+                              "@ATTRIBUTES");
+
+       ldb_test_remove_data(ldb_test_ctx, ldb_test_ctx,
+                            "cn=CaseInsensitiveValue");
+
+       ldbtest_teardown((void **) &ldb_test_ctx);
+       return 0;
+}
+
+static void test_ldb_attrs_case_insensitive(void **state)
+{
+       int cnt;
+       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+                       struct ldbtest_ctx);
+
+       /* cn matches exact case */
+       cnt = sub_search_count(ldb_test_ctx, "", "cn=CaseInsensitiveValue");
+       assert_int_equal(cnt, 1);
+
+       /* cn matches lower case */
+       cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+       assert_int_equal(cnt, 1);
+
+       /* uid matches exact case */
+       cnt = sub_search_count(ldb_test_ctx, "", "uid=CaseSensitiveValue");
+       assert_int_equal(cnt, 1);
+
+       /* uid does not match lower case */
+       cnt = sub_search_count(ldb_test_ctx, "", "uid=casesensitivevalue");
+       assert_int_equal(cnt, 0);
+}
+
+static struct ldb_schema_attribute cn_attr_1;
+static struct ldb_schema_attribute cn_attr_2;
+static struct ldb_schema_attribute default_attr;
+
+/*
+  override the name to attribute handler function
+ */
+static const struct ldb_schema_attribute *ldb_test_attribute_handler_override(struct ldb_context *ldb,
+                                                                             void *private_data,
+                                                                             const char *name)
+{
+       if (private_data != NULL && ldb_attr_cmp(name, "cn") == 0) {
                return &cn_attr_1;
        } else if (private_data == NULL && ldb_attr_cmp(name, "cn") == 0) {
                return &cn_attr_2;
@@ -2279,6 +2685,14 @@ static void test_ldb_attrs_index_handler(void **state)
                                                    syntax, &cn_attr_2);
        assert_int_equal(ret, LDB_SUCCESS);
 
+       syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING);
+       assert_non_null(syntax);
+
+       ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb,
+                                                   "", 0,
+                                                   syntax, &default_attr);
+       assert_int_equal(ret, LDB_SUCCESS);
+
        /*
         * Set an attribute handler
         */
@@ -2293,6 +2707,11 @@ static void test_ldb_attrs_index_handler(void **state)
        /* Add the index (actually any modify will do) */
        while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) {
                ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+                       ldif->msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+                       ret = ldb_modify(ldb_test_ctx->ldb,
+                                        ldif->msg);
+               }
                assert_int_equal(ret, LDB_SUCCESS);
        }
 
@@ -2378,7 +2797,8 @@ static int ldb_rename_test_setup(void **state)
 
        add_dn_with_cn(ldb_test_ctx,
                       rename_test_ctx->basedn,
-                      "test_rename_cn_val");
+                      "test_rename_cn_val",
+                      "0123456789abcde0");
 
        *state = rename_test_ctx;
        return 0;
@@ -2483,7 +2903,8 @@ static void test_ldb_rename_to_exists(void **state)
 
        add_dn_with_cn(rename_test_ctx->ldb_test_ctx,
                       new_dn,
-                      "test_rename_cn_val");
+                      "test_rename_cn_val",
+                      "0123456789abcde1");
 
        ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
                         rename_test_ctx->basedn,
@@ -2553,91 +2974,1214 @@ static void test_ldb_rename_dn_case_change(void **state)
        /* FIXME - test the values didn't change */
 }
 
-int main(int argc, const char **argv)
+static int ldb_read_only_setup(void **state)
 {
-       const struct CMUnitTest tests[] = {
-               cmocka_unit_test_setup_teardown(test_connect,
-                                               ldbtest_noconn_setup,
-                                               ldbtest_noconn_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_add,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_search,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_del,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_del_noexist,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_handle,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_build_search_req,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_transactions,
-                                               ldbtest_setup,
-                                               ldbtest_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_add_key,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_extend_key,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_add_key_noval,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_key,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_noexist_key,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_zero_vals,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_noexist_key_zero_vals,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_del_key,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_del_keyval,
-                                               ldb_modify_test_setup,
-                                               ldb_modify_test_teardown),
-               cmocka_unit_test_setup_teardown(test_search_match_none,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_search_match_one,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_search_match_filter,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_search_match_both,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_search_match_basedn,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_search_against_transaction,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_during_unindexed_search,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_during_indexed_search,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_rename_during_unindexed_search,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_rename_during_indexed_search,
-                                               ldb_search_test_setup,
-                                               ldb_search_test_teardown),
-               cmocka_unit_test_setup_teardown(test_ldb_modify_during_whole_search,
+       struct ldbtest_ctx *test_ctx;
+
+       ldbtest_setup((void **) &test_ctx);
+
+       *state = test_ctx;
+       return 0;
+}
+
+static int ldb_read_only_teardown(void **state)
+{
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       ldbtest_teardown((void **) &test_ctx);
+       return 0;
+}
+
+static void test_read_only(void **state)
+{
+       struct ldb_context *ro_ldb = NULL;
+       struct ldb_context *rw_ldb = NULL;
+       int ret;
+       TALLOC_CTX *tmp_ctx = NULL;
+
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       /*
+        * Close the ldb context freeing it this will ensure it exists on
+        * disk and can be opened in read only mode
+        */
+       TALLOC_FREE(test_ctx->ldb);
+
+       /*
+        * Open the database in read only and read write mode,
+        * ensure it's opend in read only mode first
+        */
+       ro_ldb = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(ro_ldb, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+       assert_int_equal(ret, 0);
+
+       rw_ldb = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(rw_ldb, test_ctx->dbpath, 0, NULL);
+       assert_int_equal(ret, 0);
+
+
+       /*
+        * Set up a context for the temporary variables
+        */
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       /*
+        * Ensure that we can search the read write database
+        */
+       {
+               struct ldb_result *result = NULL;
+               struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, rw_ldb,
+                                                      "dc=test");
+               assert_non_null(dn);
+
+               ret = ldb_search(rw_ldb, tmp_ctx, &result, dn,
+                                LDB_SCOPE_BASE, NULL, NULL);
+               assert_int_equal(ret, LDB_SUCCESS);
+               TALLOC_FREE(result);
+               TALLOC_FREE(dn);
+       }
+
+       /*
+        * Ensure that we can search the read only database
+        */
+       {
+               struct ldb_result *result = NULL;
+               struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, ro_ldb,
+                                                      "dc=test");
+               assert_non_null(dn);
+
+               ret = ldb_search(ro_ldb, tmp_ctx, &result, dn,
+                                LDB_SCOPE_BASE, NULL, NULL);
+               assert_int_equal(ret, LDB_SUCCESS);
+               TALLOC_FREE(result);
+               TALLOC_FREE(dn);
+       }
+       /*
+        * Ensure that a write to the read only database fails
+        */
+       {
+               struct ldb_message *msg = NULL;
+               msg = ldb_msg_new(tmp_ctx);
+               assert_non_null(msg);
+
+               msg->dn = ldb_dn_new_fmt(msg, ro_ldb, "dc=test");
+               assert_non_null(msg->dn);
+
+               ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+               assert_int_equal(ret, 0);
+
+               ret = ldb_msg_add_string(msg, "objectUUID",
+                                        "0123456789abcde1");
+               assert_int_equal(ret, LDB_SUCCESS);
+
+               ret = ldb_add(ro_ldb, msg);
+               assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM);
+               TALLOC_FREE(msg);
+       }
+
+       /*
+        * Ensure that a write to the read write database succeeds
+        */
+       {
+               struct ldb_message *msg = NULL;
+               msg = ldb_msg_new(tmp_ctx);
+               assert_non_null(msg);
+
+               msg->dn = ldb_dn_new_fmt(msg, rw_ldb, "dc=test");
+               assert_non_null(msg->dn);
+
+               ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+               assert_int_equal(ret, 0);
+
+               ret = ldb_msg_add_string(msg, "objectUUID",
+                                        "0123456789abcde2");
+               assert_int_equal(ret, LDB_SUCCESS);
+
+               ret = ldb_add(rw_ldb, msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+               TALLOC_FREE(msg);
+       }
+
+       /*
+        * Ensure that a delete from a read only database fails
+        */
+       {
+               struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, ro_ldb, "dc=test");
+               assert_non_null(dn);
+
+               ret = ldb_delete(ro_ldb, dn);
+               assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM);
+               TALLOC_FREE(dn);
+       }
+
+
+       /*
+        * Ensure that a delete from a read write succeeds
+        */
+       {
+               struct ldb_dn *dn = ldb_dn_new_fmt(tmp_ctx, rw_ldb, "dc=test");
+               assert_non_null(dn);
+
+               ret = ldb_delete(rw_ldb, dn);
+               assert_int_equal(ret, LDB_SUCCESS);
+               TALLOC_FREE(dn);
+       }
+       TALLOC_FREE(tmp_ctx);
+}
+
+static bool unique_values = false;
+
+static int unique_index_test_module_add(
+       struct ldb_module *module,
+       struct ldb_request *req)
+{
+       if (unique_values) {
+               struct ldb_message *msg = discard_const(req->op.add.message);
+               struct ldb_message_element *el = NULL;
+               el = ldb_msg_find_element(msg, "cn");
+               if (el != NULL) {
+                       el->flags |= LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX;
+               }
+       }
+
+       return ldb_next_request(module, req);
+}
+
+static int unique_index_test_module_init(struct ldb_module *module)
+{
+       return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_unique_index_test_module_ops = {
+       .name           = "unique_index_test",
+       .init_context   = unique_index_test_module_init,
+       .add            = unique_index_test_module_add,
+};
+
+static int ldb_unique_index_test_setup(void **state)
+{
+       int ret;
+       struct ldb_ldif *ldif;
+       struct ldbtest_ctx *ldb_test_ctx;
+       const char *attrs_ldif =  \
+               "dn: @ATTRIBUTES\n"
+               "cn: UNIQUE_INDEX\n"
+               "\n";
+       const char *index_ldif =  \
+               "dn: @INDEXLIST\n"
+               "@IDXATTR: cn\n"
+#ifdef GUID_IDX
+               "@IDXGUID: objectUUID\n"
+               "@IDX_DN_GUID: GUID\n"
+#endif
+               "\n";
+       const char *options[] = {"modules:unique_index_test", NULL};
+
+
+       ret = ldb_register_module(&ldb_unique_index_test_module_ops);
+       assert_true(ret == LDB_SUCCESS || ret == LDB_ERR_ENTRY_ALREADY_EXISTS);
+       ldbtest_noconn_setup((void **) &ldb_test_ctx);
+
+
+       ret = ldb_connect(ldb_test_ctx->ldb, ldb_test_ctx->dbpath, 0, options);
+       assert_int_equal(ret, 0);
+
+       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) {
+               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) {
+               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+        unique_values = true;
+
+       *state = ldb_test_ctx;
+       return 0;
+}
+
+static int ldb_unique_index_test_teardown(void **state)
+{
+       int ret;
+       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+                       struct ldbtest_ctx);
+       struct ldb_dn *del_dn;
+
+       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
+                               ldb_test_ctx->ldb,
+                               "@INDEXLIST");
+       assert_non_null(del_dn);
+
+       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
+       if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       assert_dn_doesnt_exist(ldb_test_ctx,
+                              "@INDEXLIST");
+
+       TALLOC_FREE(del_dn);
+
+       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
+                               ldb_test_ctx->ldb,
+                               "@ATTRIBUTES");
+       assert_non_null(del_dn);
+
+       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
+       if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       assert_dn_doesnt_exist(ldb_test_ctx,
+                              "@ATTRIBUTES");
+
+       ldbtest_teardown((void **) &ldb_test_ctx);
+       return 0;
+}
+
+
+static void test_ldb_add_unique_value_to_unique_index(void **state)
+{
+       int ret;
+       struct ldb_message *msg;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       msg = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg);
+
+       msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test");
+       assert_non_null(msg->dn);
+
+       ret = ldb_msg_add_string(msg, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg, "objectUUID",
+                                "0123456789abcde1");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       talloc_free(tmp_ctx);
+}
+
+static int ldb_non_unique_index_test_setup(void **state)
+{
+       int ret;
+       struct ldb_ldif *ldif;
+       struct ldbtest_ctx *ldb_test_ctx;
+       const char *index_ldif =  \
+               "dn: @INDEXLIST\n"
+               "@IDXATTR: cn\n"
+#ifdef GUID_IDX
+               "@IDXGUID: objectUUID\n"
+               "@IDX_DN_GUID: GUID\n"
+#endif
+               "\n";
+       const char *options[] = {"modules:unique_index_test", NULL};
+
+
+       ret = ldb_register_module(&ldb_unique_index_test_module_ops);
+       assert_true(ret == LDB_SUCCESS || ret == LDB_ERR_ENTRY_ALREADY_EXISTS);
+       ldbtest_noconn_setup((void **) &ldb_test_ctx);
+
+
+       ret = ldb_connect(ldb_test_ctx->ldb, ldb_test_ctx->dbpath, 0, options);
+       assert_int_equal(ret, 0);
+
+       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) {
+               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+        unique_values = true;
+
+       *state = ldb_test_ctx;
+       return 0;
+}
+
+static int ldb_non_unique_index_test_teardown(void **state)
+{
+       int ret;
+       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+                       struct ldbtest_ctx);
+       struct ldb_dn *del_dn;
+
+       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
+                               ldb_test_ctx->ldb,
+                               "@INDEXLIST");
+       assert_non_null(del_dn);
+
+       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
+       if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       assert_dn_doesnt_exist(ldb_test_ctx,
+                              "@INDEXLIST");
+
+       TALLOC_FREE(del_dn);
+
+       ldbtest_teardown((void **) &ldb_test_ctx);
+       return 0;
+}
+
+static void test_ldb_add_duplicate_value_to_unique_index(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID",
+                                "0123456789abcde1");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg02);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID",
+                                "0123456789abcde2");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
+       talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_to_index_duplicates_allowed(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+
+        unique_values = false;
+
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID",
+                                "0123456789abcde1");
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg02);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID",
+                                "0123456789abcde2");
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_SUCCESS);
+       talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_to_index_unique_values_required(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+
+        unique_values = true;
+
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID",
+                                "0123456789abcde1");
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg02);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID",
+                                "0123456789abcde2");
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
+       talloc_free(tmp_ctx);
+}
+
+static void ldb_debug_string(void *context, enum ldb_debug_level level,
+                            const char *fmt, va_list ap)
+{
+
+       if (level <= LDB_DEBUG_WARNING) {
+               *((char **)context) = talloc_vasprintf(NULL, fmt, ap);
+       }
+}
+
+static void test_ldb_unique_index_duplicate_logging(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+       char *debug_string = NULL;
+       char *p = NULL;
+
+       /* The GUID mode is not compatible with this test */
+#ifdef GUID_IDX
+       return;
+#endif
+
+       ldb_set_debug(test_ctx->ldb, ldb_debug_string, &debug_string);
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID",
+                                "0123456789abcde1");
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg02);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID",
+                                "0123456789abcde2");
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
+
+       assert_non_null(debug_string);
+       p = strstr(
+               debug_string,
+               "unique index violation on cn "
+               "in dc=test02, conficts with dc=test01 in "
+               "@INDEX:CN:test_unique_index");
+       assert_non_null(p);
+       TALLOC_FREE(debug_string);
+       talloc_free(tmp_ctx);
+}
+
+static void test_ldb_duplicate_dn_logging(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+       char *debug_string = NULL;
+
+       /* The GUID mode is not compatible with this test */
+#ifdef GUID_IDX
+       return;
+#endif
+
+       ldb_set_debug(test_ctx->ldb, ldb_debug_string, &debug_string);
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index01");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID",
+                                "0123456789abcde1");
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg02);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index02");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID",
+                                "0123456789abcde2");
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS);
+
+       assert_null(debug_string);
+       talloc_free(tmp_ctx);
+}
+
+static int ldb_guid_index_test_setup(void **state)
+{
+       int ret;
+       struct ldb_ldif *ldif;
+       struct ldbtest_ctx *ldb_test_ctx;
+       const char *attrs_ldif =  \
+               "dn: @ATTRIBUTES\n"
+               "cn: UNIQUE_INDEX\n"
+               "\n";
+       const char *index_ldif =  \
+               "dn: @INDEXLIST\n"
+               "@IDXATTR: cn\n"
+               "@IDXGUID: objectUUID\n"
+               "@IDX_DN_GUID: GUID\n"
+               "\n";
+
+       ldbtest_noconn_setup((void **) &ldb_test_ctx);
+
+
+       ret = ldb_connect(ldb_test_ctx->ldb, ldb_test_ctx->dbpath, 0, NULL);
+       assert_int_equal(ret, 0);
+
+       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &attrs_ldif))) {
+               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) {
+               ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       *state = ldb_test_ctx;
+       return 0;
+}
+
+static int ldb_guid_index_test_teardown(void **state)
+{
+       int ret;
+       struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+                       struct ldbtest_ctx);
+       struct ldb_dn *del_dn;
+
+       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
+                               ldb_test_ctx->ldb,
+                               "@INDEXLIST");
+       assert_non_null(del_dn);
+
+       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
+       if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       assert_dn_doesnt_exist(ldb_test_ctx,
+                              "@INDEXLIST");
+
+       TALLOC_FREE(del_dn);
+
+       del_dn = ldb_dn_new_fmt(ldb_test_ctx,
+                               ldb_test_ctx->ldb,
+                               "@ATTRIBUTES");
+       assert_non_null(del_dn);
+
+       ret = ldb_delete(ldb_test_ctx->ldb, del_dn);
+       if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+               assert_int_equal(ret, LDB_SUCCESS);
+       }
+
+       assert_dn_doesnt_exist(ldb_test_ctx,
+                              "@ATTRIBUTES");
+
+       ldbtest_teardown((void **) &ldb_test_ctx);
+       return 0;
+}
+
+
+static void test_ldb_unique_index_duplicate_with_guid(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+       char *debug_string = NULL;
+       char *p = NULL;
+
+       ldb_set_debug(test_ctx->ldb, ldb_debug_string, &debug_string);
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID", "0123456789abcdef");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test02");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID", "0123456789abcde0");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
+
+       assert_non_null(debug_string);
+       p = strstr(
+               debug_string,
+               "unique index violation on cn in dc=test02, conficts with "
+               "objectUUID 0123456789abcdef in @INDEX:CN:test_unique_index");
+       assert_non_null(p);
+       TALLOC_FREE(debug_string);
+       talloc_free(tmp_ctx);
+}
+
+static void test_ldb_guid_index_duplicate_dn_logging(void **state)
+{
+       int ret;
+       struct ldb_message *msg01;
+       struct ldb_message *msg02;
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       TALLOC_CTX *tmp_ctx;
+       char *debug_string = NULL;
+
+       ldb_set_debug(test_ctx->ldb, ldb_debug_string, &debug_string);
+       tmp_ctx = talloc_new(test_ctx);
+       assert_non_null(tmp_ctx);
+
+       msg01 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg01);
+
+       msg01->dn = ldb_dn_new_fmt(msg01, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg01->dn);
+
+       ret = ldb_msg_add_string(msg01, "cn", "test_unique_index01");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg01, "objectUUID", "0123456789abcdef");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg01);
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       msg02 = ldb_msg_new(tmp_ctx);
+       assert_non_null(msg02);
+
+       msg02->dn = ldb_dn_new_fmt(msg02, test_ctx->ldb, "dc=test01");
+       assert_non_null(msg02->dn);
+
+       ret = ldb_msg_add_string(msg02, "cn", "test_unique_index02");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_msg_add_string(msg02, "objectUUID", "0123456789abcde1");
+       assert_int_equal(ret, LDB_SUCCESS);
+
+       ret = ldb_add(test_ctx->ldb, msg02);
+       assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS);
+
+       assert_null(debug_string);
+       talloc_free(tmp_ctx);
+}
+
+static void test_ldb_talloc_destructor_transaction_cleanup(void **state)
+{
+       struct ldbtest_ctx *test_ctx = NULL;
+
+       test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+       assert_non_null(test_ctx);
+
+       ldb_transaction_start(test_ctx->ldb);
+
+       /*
+        * Trigger the destructor
+        */
+       TALLOC_FREE(test_ctx->ldb);
+
+       /*
+        * Now ensure that a new connection can be opened
+        */
+       {
+               TALLOC_CTX *tctx = talloc_new(test_ctx);
+               struct ldbtest_ctx *ctx = talloc_zero(tctx, struct ldbtest_ctx);
+               struct ldb_dn *basedn;
+               struct ldb_result *result = NULL;
+               int ret;
+
+               ldbtest_setup((void *)&ctx);
+
+               basedn = ldb_dn_new_fmt(tctx, ctx->ldb, "dc=test");
+               assert_non_null(basedn);
+
+               ret = ldb_search(ctx->ldb,
+                                tctx,
+                                &result,
+                                basedn,
+                                LDB_SCOPE_BASE,
+                                NULL,
+                                NULL);
+               assert_int_equal(ret, 0);
+               assert_non_null(result);
+               assert_int_equal(result->count, 0);
+
+               ldbtest_teardown((void *)&ctx);
+       }
+}
+
+static void test_transaction_start_across_fork(void **state)
+{
+       struct ldb_context *ldb1 = NULL;
+       int ret;
+       struct ldbtest_ctx *test_ctx = NULL;
+       int pipes[2];
+       char buf[2];
+       int wstatus;
+       pid_t pid, child_pid;
+
+       test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+       /*
+        * Open the database
+        */
+       ldb1 = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL);
+       assert_int_equal(ret, 0);
+
+       ret = pipe(pipes);
+       assert_int_equal(ret, 0);
+
+       child_pid = fork();
+       if (child_pid == 0) {
+               close(pipes[0]);
+               ret = ldb_transaction_start(ldb1);
+               if (ret != LDB_ERR_PROTOCOL_ERROR) {
+                       print_error(__location__": ldb_transaction_start "
+                                   "returned (%d) %s\n",
+                                   ret,
+                                   ldb1->err_string);
+                       exit(LDB_ERR_OTHER);
+               }
+
+               ret = write(pipes[1], "GO", 2);
+               if (ret != 2) {
+                       print_error(__location__
+                                     " write returned (%d)",
+                                     ret);
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+               exit(LDB_SUCCESS);
+       }
+       close(pipes[1]);
+       ret = read(pipes[0], buf, 2);
+       assert_int_equal(ret, 2);
+
+       pid = waitpid(child_pid, &wstatus, 0);
+       assert_int_equal(pid, child_pid);
+
+       assert_true(WIFEXITED(wstatus));
+
+       assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+static void test_transaction_commit_across_fork(void **state)
+{
+       struct ldb_context *ldb1 = NULL;
+       int ret;
+       struct ldbtest_ctx *test_ctx = NULL;
+       int pipes[2];
+       char buf[2];
+       int wstatus;
+       pid_t pid, child_pid;
+
+       test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+       /*
+        * Open the database
+        */
+       ldb1 = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL);
+       assert_int_equal(ret, 0);
+
+       ret = ldb_transaction_start(ldb1);
+       assert_int_equal(ret, 0);
+
+       ret = pipe(pipes);
+       assert_int_equal(ret, 0);
+
+       child_pid = fork();
+       if (child_pid == 0) {
+               close(pipes[0]);
+               ret = ldb_transaction_commit(ldb1);
+
+               if (ret != LDB_ERR_PROTOCOL_ERROR) {
+                       print_error(__location__": ldb_transaction_commit "
+                                   "returned (%d) %s\n",
+                                   ret,
+                                   ldb1->err_string);
+                       exit(LDB_ERR_OTHER);
+               }
+
+               ret = write(pipes[1], "GO", 2);
+               if (ret != 2) {
+                       print_error(__location__
+                                     " write returned (%d)",
+                                     ret);
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+               exit(LDB_SUCCESS);
+       }
+       close(pipes[1]);
+       ret = read(pipes[0], buf, 2);
+       assert_int_equal(ret, 2);
+
+       pid = waitpid(child_pid, &wstatus, 0);
+       assert_int_equal(pid, child_pid);
+
+       assert_true(WIFEXITED(wstatus));
+
+       assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+static void test_lock_read_across_fork(void **state)
+{
+       struct ldb_context *ldb1 = NULL;
+       int ret;
+       struct ldbtest_ctx *test_ctx = NULL;
+       int pipes[2];
+       char buf[2];
+       int wstatus;
+       pid_t pid, child_pid;
+
+       test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+       /*
+        * Open the database
+        */
+       ldb1 = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL);
+       assert_int_equal(ret, 0);
+
+       ret = pipe(pipes);
+       assert_int_equal(ret, 0);
+
+       child_pid = fork();
+       if (child_pid == 0) {
+               struct ldb_dn *basedn;
+               struct ldb_result *result = NULL;
+
+               close(pipes[0]);
+
+               basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "dc=test");
+               assert_non_null(basedn);
+
+               ret = ldb_search(test_ctx->ldb,
+                                test_ctx,
+                                &result,
+                                basedn,
+                                LDB_SCOPE_BASE,
+                                NULL,
+                                NULL);
+               if (ret != LDB_ERR_PROTOCOL_ERROR) {
+                       print_error(__location__": ldb_search "
+                                   "returned (%d) %s\n",
+                                   ret,
+                                   ldb1->err_string);
+                       exit(LDB_ERR_OTHER);
+               }
+
+               ret = write(pipes[1], "GO", 2);
+               if (ret != 2) {
+                       print_error(__location__
+                                     " write returned (%d)",
+                                     ret);
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+               exit(LDB_SUCCESS);
+       }
+       close(pipes[1]);
+       ret = read(pipes[0], buf, 2);
+       assert_int_equal(ret, 2);
+
+       pid = waitpid(child_pid, &wstatus, 0);
+       assert_int_equal(pid, child_pid);
+
+       assert_true(WIFEXITED(wstatus));
+
+       assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+       {
+               /*
+                * Ensure that the search actually succeeds on the opening
+                * pid
+                */
+               struct ldb_dn *basedn;
+               struct ldb_result *result = NULL;
+
+               close(pipes[0]);
+
+               basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "dc=test");
+               assert_non_null(basedn);
+
+               ret = ldb_search(test_ctx->ldb,
+                                test_ctx,
+                                &result,
+                                basedn,
+                                LDB_SCOPE_BASE,
+                                NULL,
+                                NULL);
+               assert_int_equal(0, ret);
+       }
+}
+
+static void test_multiple_opens_across_fork(void **state)
+{
+       struct ldb_context *ldb1 = NULL;
+       struct ldb_context *ldb2 = NULL;
+       int ret;
+       struct ldbtest_ctx *test_ctx = NULL;
+       int pipes[2];
+       char buf[2];
+       int wstatus;
+       pid_t pid, child_pid;
+
+       test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+       /*
+        * Open the database again
+        */
+       ldb1 = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+       assert_int_equal(ret, 0);
+
+       ldb2 = ldb_init(test_ctx, test_ctx->ev);
+       ret = ldb_connect(ldb2, test_ctx->dbpath, 0, NULL);
+       assert_int_equal(ret, 0);
+
+       ret = pipe(pipes);
+       assert_int_equal(ret, 0);
+
+       child_pid = fork();
+       if (child_pid == 0) {
+               struct ldb_context *ldb3 = NULL;
+
+               close(pipes[0]);
+               ldb3 = ldb_init(test_ctx, test_ctx->ev);
+               ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+               if (ret != 0) {
+                       print_error(__location__": ldb_connect returned (%d)\n",
+                                   ret);
+                       exit(ret);
+               }
+               ret = write(pipes[1], "GO", 2);
+               if (ret != 2) {
+                       print_error(__location__
+                                     " write returned (%d)",
+                                     ret);
+                       exit(LDB_ERR_OPERATIONS_ERROR);
+               }
+               exit(LDB_SUCCESS);
+       }
+       close(pipes[1]);
+       ret = read(pipes[0], buf, 2);
+       assert_int_equal(ret, 2);
+
+       pid = waitpid(child_pid, &wstatus, 0);
+       assert_int_equal(pid, child_pid);
+
+       assert_true(WIFEXITED(wstatus));
+
+       assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+int main(int argc, const char **argv)
+{
+       const struct CMUnitTest tests[] = {
+               cmocka_unit_test_setup_teardown(test_connect,
+                                               ldbtest_noconn_setup,
+                                               ldbtest_noconn_teardown),
+               cmocka_unit_test_setup_teardown(test_ldif_message,
+                                               ldbtest_noconn_setup,
+                                               ldbtest_noconn_teardown),
+               cmocka_unit_test_setup_teardown(test_ldif_message_redacted,
+                                               ldbtest_noconn_setup,
+                                               ldbtest_noconn_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_add,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_search,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_del,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_del_noexist,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_handle,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_build_search_req,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_transactions,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_nested_transactions,
+                                               ldbtest_setup,
+                                               ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_add_key,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_extend_key,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_add_key_noval,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_key,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_noexist_key,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_zero_vals,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_replace_noexist_key_zero_vals,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_del_key,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_del_keyval,
+                                               ldb_modify_test_setup,
+                                               ldb_modify_test_teardown),
+               cmocka_unit_test_setup_teardown(test_search_match_none,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_search_match_one,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_search_match_filter,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_search_match_both,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_search_match_basedn,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_search_against_transaction,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_during_unindexed_search,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_during_indexed_search,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_rename_during_unindexed_search,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_rename_during_indexed_search,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_during_whole_search,
+                                               ldb_search_test_setup,
+                                               ldb_search_test_teardown),
+               cmocka_unit_test_setup_teardown(test_ldb_modify_before_ldb_wait,
                                                ldb_search_test_setup,
                                                ldb_search_test_teardown),
                cmocka_unit_test_setup_teardown(test_ldb_attrs_case_insensitive,
@@ -2664,6 +4208,62 @@ int main(int argc, const char **argv)
                cmocka_unit_test_setup_teardown(test_ldb_rename_dn_case_change,
                                                ldb_rename_test_setup,
                                                ldb_rename_test_teardown),
+               cmocka_unit_test_setup_teardown(test_read_only,
+                                               ldb_read_only_setup,
+                                               ldb_read_only_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_add_unique_value_to_unique_index,
+                       ldb_unique_index_test_setup,
+                       ldb_unique_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_add_duplicate_value_to_unique_index,
+                       ldb_unique_index_test_setup,
+                       ldb_unique_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_add_to_index_duplicates_allowed,
+                       ldb_non_unique_index_test_setup,
+                       ldb_non_unique_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_add_to_index_unique_values_required,
+                       ldb_non_unique_index_test_setup,
+                       ldb_non_unique_index_test_teardown),
+               /* These tests are not compatible with mdb */
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_unique_index_duplicate_logging,
+                       ldb_unique_index_test_setup,
+                       ldb_unique_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_duplicate_dn_logging,
+                       ldb_unique_index_test_setup,
+                       ldb_unique_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_guid_index_duplicate_dn_logging,
+                       ldb_guid_index_test_setup,
+                       ldb_guid_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_unique_index_duplicate_with_guid,
+                       ldb_guid_index_test_setup,
+                       ldb_guid_index_test_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_ldb_talloc_destructor_transaction_cleanup,
+                       ldbtest_setup,
+                       ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_transaction_start_across_fork,
+                       ldbtest_setup,
+                       ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_transaction_commit_across_fork,
+                       ldbtest_setup,
+                       ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_lock_read_across_fork,
+                       ldbtest_setup,
+                       ldbtest_teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_multiple_opens_across_fork,
+                       ldbtest_setup,
+                       ldbtest_teardown),
        };
 
        return cmocka_run_group_tests(tests, NULL, NULL);