summaryrefslogtreecommitdiffstats
path: root/lib/ldb/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /lib/ldb/tests
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/ldb/tests/guidindexpackv1.ldbbin0 -> 65536 bytes
-rw-r--r--lib/ldb/tests/init.ldif41
-rwxr-xr-xlib/ldb/tests/init_slapd.sh40
-rwxr-xr-xlib/ldb/tests/kill_slapd.sh12
-rwxr-xr-xlib/ldb/tests/ldapi_url.sh11
-rw-r--r--lib/ldb/tests/ldb_filter_attrs_in_place_test.c940
-rw-r--r--lib/ldb/tests/ldb_filter_attrs_test.c989
-rw-r--r--lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind97
-rw-r--r--lib/ldb/tests/ldb_key_value_sub_txn_test.c844
-rw-r--r--lib/ldb/tests/ldb_key_value_test.c388
-rw-r--r--lib/ldb/tests/ldb_kv_ops_test.c1819
-rw-r--r--lib/ldb/tests/ldb_kv_ops_test.valgrind97
-rw-r--r--lib/ldb/tests/ldb_lmdb_free_list_test.c661
-rw-r--r--lib/ldb/tests/ldb_lmdb_size_test.c249
-rw-r--r--lib/ldb/tests/ldb_lmdb_test.c590
-rw-r--r--lib/ldb/tests/ldb_match_test.c313
-rw-r--r--lib/ldb/tests/ldb_match_test.valgrind16
-rw-r--r--lib/ldb/tests/ldb_mod_op_test.c4724
-rw-r--r--lib/ldb/tests/ldb_msg.c380
-rw-r--r--lib/ldb/tests/ldb_no_lmdb_test.c159
-rw-r--r--lib/ldb/tests/ldb_parse_test.c172
-rw-r--r--lib/ldb/tests/ldb_tdb_test.c389
-rw-r--r--lib/ldb/tests/lldb_ldap.c105
-rw-r--r--lib/ldb/tests/photo.ldif5
-rwxr-xr-xlib/ldb/tests/python/api.py3858
-rw-r--r--lib/ldb/tests/python/crash.py45
-rwxr-xr-xlib/ldb/tests/python/index.py1454
-rw-r--r--lib/ldb/tests/python/repack.py204
-rw-r--r--lib/ldb/tests/samba4.pngbin0 -> 6239 bytes
-rw-r--r--lib/ldb/tests/sample_module.c122
-rw-r--r--lib/ldb/tests/schema-tests/schema-add-test.ldif66
-rw-r--r--lib/ldb/tests/schema-tests/schema-mod-test-1.ldif5
-rw-r--r--lib/ldb/tests/schema-tests/schema-mod-test-2.ldif5
-rw-r--r--lib/ldb/tests/schema-tests/schema-mod-test-3.ldif5
-rw-r--r--lib/ldb/tests/schema-tests/schema-mod-test-4.ldif5
-rw-r--r--lib/ldb/tests/schema-tests/schema-mod-test-5.ldif5
-rw-r--r--lib/ldb/tests/schema-tests/schema.ldif100
-rw-r--r--lib/ldb/tests/slapd.conf26
-rwxr-xr-xlib/ldb/tests/start_slapd.sh14
-rw-r--r--lib/ldb/tests/test-attribs.ldif6
-rw-r--r--lib/ldb/tests/test-config.ldif67
-rwxr-xr-xlib/ldb/tests/test-controls.sh43
-rw-r--r--lib/ldb/tests/test-default-config.ldif17
-rw-r--r--lib/ldb/tests/test-dup-2.ldif6
-rw-r--r--lib/ldb/tests/test-dup.ldif13
-rwxr-xr-xlib/ldb/tests/test-extended.sh69
-rwxr-xr-xlib/ldb/tests/test-generic.sh158
-rw-r--r--lib/ldb/tests/test-index.ldif7
-rwxr-xr-xlib/ldb/tests/test-ldap.sh54
-rw-r--r--lib/ldb/tests/test-modify-modrdn.ldif12
-rw-r--r--lib/ldb/tests/test-modify-unmet-2.ldif7
-rw-r--r--lib/ldb/tests/test-modify-unmet.ldif15
-rw-r--r--lib/ldb/tests/test-modify.ldif23
-rwxr-xr-xlib/ldb/tests/test-schema.sh33
-rwxr-xr-xlib/ldb/tests/test-soloading.sh31
-rwxr-xr-xlib/ldb/tests/test-sqlite3.sh23
-rw-r--r--lib/ldb/tests/test-tdb-features.sh179
-rwxr-xr-xlib/ldb/tests/test-tdb-subunit.sh7
-rwxr-xr-xlib/ldb/tests/test-tdb.sh38
-rw-r--r--lib/ldb/tests/test-wildcard.ldif5
-rw-r--r--lib/ldb/tests/test-wrong_attributes.ldif3
-rw-r--r--lib/ldb/tests/test.ldif440
-rw-r--r--lib/ldb/tests/test_ldb_dn.c232
-rw-r--r--lib/ldb/tests/test_ldb_qsort.c65
-rw-r--r--lib/ldb/tests/testdata.txt8
-rw-r--r--lib/ldb/tests/testsearch.txt5
66 files changed, 20521 insertions, 0 deletions
diff --git a/lib/ldb/tests/guidindexpackv1.ldb b/lib/ldb/tests/guidindexpackv1.ldb
new file mode 100644
index 0000000..96d783c
--- /dev/null
+++ b/lib/ldb/tests/guidindexpackv1.ldb
Binary files differ
diff --git a/lib/ldb/tests/init.ldif b/lib/ldb/tests/init.ldif
new file mode 100644
index 0000000..97b4561
--- /dev/null
+++ b/lib/ldb/tests/init.ldif
@@ -0,0 +1,41 @@
+dn: o=University of Michigan,c=TEST
+objectclass: organization
+objectclass: domainRelatedObject
+l: Ann Arbor, Michigan
+st: Michigan
+o: University of Michigan
+o: UMICH
+o: UM
+o: U-M
+o: U of M
+description: The University of Michigan at Ann Arbor
+postaladdress: University of Michigan $ 535 W. William St. $ Ann Arbor, MI 481
+ 09 $ US
+telephonenumber: +1 313 764-1817
+associateddomain: example.com
+
+# there was an empty "seeAlso" here
+
+dn: ou=People,o=University of Michigan,c=TEST
+objectclass: organizationalUnit
+objectclass: extensibleObject
+ou: People
+uidNumber: 0
+gidNumber: 0
+
+dn: ou=Ldb Test,ou=People,o=University of Michigan,c=TEST
+objectclass: organizationalUnit
+objectclass: extensibleObject
+ou: People
+ou: Ldb Test
+uidNumber: 0
+gidNumber: 0
+
+dn: ou=LdbTspace,ou=People,o=University of Michigan,c=TEST
+objectclass: organizationalUnit
+objectclass: extensibleObject
+ou: People
+ou: LdbTspace
+description: test white space removal in comparisons
+uidNumber: 0
+gidNumber: 0
diff --git a/lib/ldb/tests/init_slapd.sh b/lib/ldb/tests/init_slapd.sh
new file mode 100755
index 0000000..4629cf1
--- /dev/null
+++ b/lib/ldb/tests/init_slapd.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+rm -rf tests/tmp/db
+mkdir -p tests/tmp/db
+
+if [ -f tests/tmp/slapd.pid ]; then
+ kill $(cat tests/tmp/slapd.pid)
+ sleep 1
+fi
+if [ -f tests/tmp/slapd.pid ]; then
+ kill -9 $(cat tests/tmp/slapd.pid)
+ rm -f tests/tmp/slapd.pid
+fi
+
+# we don't consider a slapadd failure as a test suite failure, as it
+# has nothing to do with ldb
+
+MODCONF=tests/tmp/modules.conf
+rm -f $MODCONF
+touch $MODCONF || exit 1
+
+slaptest -u -f $LDBDIR/tests/slapd.conf >/dev/null 2>&1 || {
+ echo "enabling sladp modules"
+ cat >$MODCONF <<EOF
+modulepath /usr/lib/ldap
+moduleload back_bdb
+EOF
+}
+
+slaptest -u -f $LDBDIR/tests/slapd.conf || {
+ echo "slaptest failed - skipping ldap tests"
+ exit 0
+}
+
+slapadd -f $LDBDIR/tests/slapd.conf <$LDBDIR/tests/init.ldif || exit 0
diff --git a/lib/ldb/tests/kill_slapd.sh b/lib/ldb/tests/kill_slapd.sh
new file mode 100755
index 0000000..c60ae33
--- /dev/null
+++ b/lib/ldb/tests/kill_slapd.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+if [ -f tests/tmp/slapd.pid ]; then
+ echo "killing slapd process $(cat tests/tmp/slapd.pid)"
+ kill -9 $(cat tests/tmp/slapd.pid)
+ rm -f tests/tmp/slapd.pid
+fi
diff --git a/lib/ldb/tests/ldapi_url.sh b/lib/ldb/tests/ldapi_url.sh
new file mode 100755
index 0000000..bcda917
--- /dev/null
+++ b/lib/ldb/tests/ldapi_url.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# aargh, did LDAP ever have to expose this crap to users ...
+
+BASE=$(pwd)
+
+TMPDIR=$BASE/tests/tmp
+
+LDAPI_ESCAPE=$(echo $TMPDIR/ldapi | sed 's|/|%2F|g')
+
+echo "ldapi://$LDAPI_ESCAPE"
diff --git a/lib/ldb/tests/ldb_filter_attrs_in_place_test.c b/lib/ldb/tests/ldb_filter_attrs_in_place_test.c
new file mode 100644
index 0000000..bf37016
--- /dev/null
+++ b/lib/ldb/tests/ldb_filter_attrs_in_place_test.c
@@ -0,0 +1,940 @@
+/*
+ * Tests exercising ldb_filter_attrs_in_place().
+ *
+ *
+ * Copyright (C) Catalyst.NET Ltd 2017
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "../include/ldb.h"
+#include "../include/ldb_module.h"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+};
+
+/*
+ * NOTE WELL:
+ *
+ * This test checks the current behaviour of the function, however
+ * this is not in a public ABI and many of the tested behaviours are
+ * not ideal. If the behaviour is deliberately improved, this test
+ * should be updated without worry to the new better behaviour.
+ *
+ * In particular the test is particularly to ensure the current
+ * behaviour is memory-safe.
+ */
+
+static int setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ talloc_free(*state);
+ return 0;
+}
+
+static void msg_add_dn(struct ldb_message *msg)
+{
+ const char *dn_attr = "distinguishedName";
+ char *dn = NULL;
+ int ret;
+
+ assert_null(ldb_msg_find_element(msg, dn_attr));
+
+ assert_non_null(msg->dn);
+ dn = ldb_dn_alloc_linearized(msg, msg->dn);
+ assert_non_null(dn);
+
+ /*
+ * The message's elements must be talloc allocated to call
+ * ldb_msg_add_steal_string().
+ */
+ msg->elements = talloc_memdup(msg,
+ msg->elements,
+ msg->num_elements * sizeof(msg->elements[0]));
+ assert_non_null(msg->elements);
+
+ ret = ldb_msg_add_steal_string(msg, dn_attr, dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+}
+
+/*
+ * Test against a record with only one attribute, matching the one in
+ * the list
+ */
+static void test_filter_attrs_in_place_one_attr_matched(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"foo", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_non_null(msg->dn);
+ assert_int_equal(msg->num_elements, 1);
+ assert_string_equal(msg->elements[0].name, "foo");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value, strlen(value));
+}
+
+/*
+ * Test against a record with only one attribute, matching the one of
+ * the multiple attributes in the list
+ */
+static void test_filter_attrs_in_place_one_attr_matched_of_many(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"foo", "bar", "baz", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_non_null(msg->dn);
+ assert_int_equal(msg->num_elements, 1);
+ assert_string_equal(msg->elements[0].name, "foo");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value, strlen(value));
+}
+
+/*
+ * Test against a record with only one attribute, matching both
+ * attributes in the list
+ */
+static void test_filter_attrs_in_place_two_attr_matched_attrs(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ /* deliberately the other order */
+ const char *attrs[] = {"bar", "foo", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 2);
+
+ assert_non_null(msg->dn);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(msg->elements[0].name, "foo");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(msg->elements[1].name, "bar");
+ assert_int_equal(msg->elements[1].num_values, 1);
+ assert_int_equal(msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, only of which is in
+ * the list
+ */
+static void test_filter_attrs_in_place_two_attr_matched_one_attr(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"bar", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ struct ldb_message_element elements[] = {
+ {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 1);
+
+ assert_non_null(msg->dn);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(msg->elements[0].name, "bar");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value2));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, both matching the one
+ * specified attribute in the list (a corrupt record)
+ */
+static void test_filter_attrs_in_place_two_dup_attr_matched_one_attr(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"bar", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+
+ /* Both elements match the filter */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 2);
+
+ assert_non_null(msg->dn);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(msg->elements[0].name, "bar");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value1, strlen(value1));
+
+ assert_string_equal(msg->elements[1].name, "bar");
+ assert_int_equal(msg->elements[1].num_values, 1);
+ assert_int_equal(msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, both matching the one
+ * specified attribute in the list (a corrupt record)
+ */
+static void test_filter_attrs_in_place_two_dup_attr_matched_dup(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"bar", "bar", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+
+ /* This does not fail the pidgenhole test */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 2);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(msg->elements[0].name, "bar");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(msg->elements[1].name, "bar");
+ assert_int_equal(msg->elements[1].num_values, 1);
+ assert_int_equal(msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, both matching one of the
+ * specified attributes in the list (a corrupt record)
+ */
+static void test_filter_attrs_in_place_two_dup_attr_matched_one_of_two(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"bar", "foo", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+
+ /* This does not fail the pidgenhole test */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 2);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(msg->elements[0].name, "bar");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(msg->elements[1].name, "bar");
+ assert_int_equal(msg->elements[1].num_values, 1);
+ assert_int_equal(msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes against * (but not the
+ * other named attribute) (a corrupt record)
+ */
+static void test_filter_attrs_in_place_two_dup_attr_matched_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", "foo", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+
+ /* This does not fail the pidgenhole test */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 3);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(msg->elements[0].name, "bar");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_int_equal(msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(msg->elements[1].name, "bar");
+ assert_int_equal(msg->elements[1].num_values, 1);
+ assert_int_equal(msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(msg->elements[1].values[0].data,
+ value2, strlen(value2));
+
+ assert_non_null(msg->dn);
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(msg->dn));
+}
+
+/*
+ * Test against a record with only one attribute, matching the * in
+ * the list
+ */
+static void test_filter_attrs_in_place_one_attr_matched_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 2);
+
+ assert_non_null(msg->dn);
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(msg->dn));
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "foo",
+ NULL),
+ value);
+}
+
+/*
+ * Test against a record with two attributes, matching the * in
+ * the list
+ */
+static void test_filter_attrs_in_place_two_attr_matched_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+ struct ldb_message_element elements[] = {
+ {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 2;
+ msg->elements = elements;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 3);
+
+ assert_non_null(msg->dn);
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(msg->dn));
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "foo",
+ NULL),
+ value1);
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "bar",
+ NULL),
+ value2);
+}
+
+/*
+ * Test against a record with only one attribute, matching the * in
+ * the list, but without the DN being pre-filled. Succeeds, but the
+ * distinguishedName is not added.
+ */
+static void test_filter_attrs_in_place_one_attr_matched_star_no_dn(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = NULL;
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_null(msg->dn);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 1);
+}
+
+/*
+ * Test against a record with only one attribute, matching the * in
+ * the list plus requsesting distinguishedName
+ */
+static void test_filter_attrs_in_place_one_attr_matched_star_dn(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", "distinguishedName", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 2);
+
+ assert_non_null(msg->dn);
+
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(msg->dn));
+ assert_string_equal(ldb_msg_find_attr_as_string(msg,
+ "foo",
+ NULL),
+ value);
+}
+
+/*
+ * Test against a record with only one attribute, but returning
+ * distinguishedName from the list (only)
+ */
+static void test_filter_attrs_in_place_one_attr_matched_dn(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"distinguishedName", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 1);
+
+ assert_non_null(msg->dn);
+ assert_string_equal(msg->elements[0].name, "distinguishedName");
+ assert_int_equal(msg->elements[0].num_values, 1);
+ assert_string_equal((char *)msg->elements[0].values[0].data,
+ ldb_dn_get_linearized(msg->dn));
+}
+
+/*
+ * Test against a record with only one attribute, not matching the
+ * empty attribute list
+ */
+static void test_filter_attrs_in_place_one_attr_empty_list(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+
+ assert_non_null(msg);
+ msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org");
+ msg->num_elements = 1;
+ msg->elements = &element_1;
+
+ assert_non_null(msg->dn);
+ msg_add_dn(msg);
+
+ ret = ldb_filter_attrs_in_place(msg, attrs);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, 0);
+ assert_non_null(msg->dn);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_matched,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_matched_of_many,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_attr_matched_attrs,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_attr_matched_one_attr,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_dup_attr_matched_one_attr,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_dup_attr_matched_dup,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_dup_attr_matched_one_of_two,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_dup_attr_matched_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_matched_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_two_attr_matched_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_matched_star_no_dn,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_matched_star_dn,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_matched_dn,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_in_place_one_attr_empty_list,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_filter_attrs_test.c b/lib/ldb/tests/ldb_filter_attrs_test.c
new file mode 100644
index 0000000..291350a
--- /dev/null
+++ b/lib/ldb/tests/ldb_filter_attrs_test.c
@@ -0,0 +1,989 @@
+/*
+ * Tests exercising the ldb_filter_attrs().
+ *
+ *
+ * Copyright (C) Catalyst.NET Ltd 2017
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "../include/ldb.h"
+#include "../include/ldb_module.h"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+};
+
+/*
+ * NOTE WELL:
+ *
+ * This test checks the current behaviour of the function, however
+ * this is not in a public ABI and many of the tested behaviours are
+ * not ideal. If the behaviour is deliberately improved, this test
+ * should be updated without worry to the new better behaviour.
+ *
+ * In particular the test is particularly to ensure the current
+ * behaviour is memory-safe.
+ */
+
+static int setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ talloc_free(*state);
+ return 0;
+}
+
+
+/*
+ * Test against a record with only one attribute, matching the one in
+ * the list
+ */
+static void test_filter_attrs_one_attr_matched(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"foo", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+
+ /*
+ * assert the ldb_filter_attrs does not read or modify
+ * filtered_msg.dn in this case
+ */
+ assert_null(filtered_msg->dn);
+ assert_int_equal(filtered_msg->num_elements, 1);
+ assert_string_equal(filtered_msg->elements[0].name, "foo");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value, strlen(value));
+}
+
+/*
+ * Test against a record with only one attribute, matching the one of
+ * the multiple attributes in the list
+ */
+static void test_filter_attrs_one_attr_matched_of_many(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"foo", "bar", "baz", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+
+ /*
+ * assert the ldb_filter_attrs does not read or modify
+ * filtered_msg.dn in this case
+ */
+ assert_null(filtered_msg->dn);
+ assert_int_equal(filtered_msg->num_elements, 1);
+ assert_string_equal(filtered_msg->elements[0].name, "foo");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value, strlen(value));
+}
+
+/*
+ * Test against a record with only one attribute, matching both
+ * attributes in the list
+ */
+static void test_filter_attrs_two_attr_matched_attrs(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ /* deliberately the other order */
+ const char *attrs[] = {"bar", "foo", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 2);
+
+ /*
+ * assert the ldb_filter_attrs does not read or modify
+ * filtered_msg.dn in this case
+ */
+ assert_null(filtered_msg->dn);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(filtered_msg->elements[0].name, "foo");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(filtered_msg->elements[1].name, "bar");
+ assert_int_equal(filtered_msg->elements[1].num_values, 1);
+ assert_int_equal(filtered_msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(filtered_msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, only of which is in
+ * the list
+ */
+static void test_filter_attrs_two_attr_matched_one_attr(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ /* deliberately the other order */
+ const char *attrs[] = {"bar", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 1);
+
+ /*
+ * assert the ldb_filter_attrs does not read or modify
+ * filtered_msg.dn in this case
+ */
+ assert_null(filtered_msg->dn);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(filtered_msg->elements[0].name, "bar");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value2));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, both matching the one
+ * specified attribute in the list (a corrupt record)
+ */
+static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ /* deliberately the other order */
+ const char *attrs[] = {"bar", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+
+ /* This should fail the pidgenhole test */
+ assert_int_equal(ret, -1);
+ assert_null(filtered_msg->elements);
+}
+
+/*
+ * Test against a record with two attributes, both matching the one
+ * specified attribute in the list (a corrupt record)
+ */
+static void test_filter_attrs_two_dup_attr_matched_dup(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"bar", "bar", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+
+ /* This does not fail the pidgenhole test */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(filtered_msg->num_elements, 2);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(filtered_msg->elements[0].name, "bar");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(filtered_msg->elements[1].name, "bar");
+ assert_int_equal(filtered_msg->elements[1].num_values, 1);
+ assert_int_equal(filtered_msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(filtered_msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes, both matching one of the
+ * specified attributes in the list (a corrupt record)
+ */
+static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"bar", "foo", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+
+ /* This does not fail the pidgenhole test */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(filtered_msg->num_elements, 2);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(filtered_msg->elements[0].name, "bar");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(filtered_msg->elements[1].name, "bar");
+ assert_int_equal(filtered_msg->elements[1].num_values, 1);
+ assert_int_equal(filtered_msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(filtered_msg->elements[1].values[0].data,
+ value2, strlen(value2));
+}
+
+/*
+ * Test against a record with two attributes against * (but not the
+ * other named attribute) (a corrupt record)
+ */
+static void test_filter_attrs_two_dup_attr_matched_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", "foo", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+
+ /* foo and bar are the other order to in attrs */
+ struct ldb_message_element elements[] = {
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ /* Needed as * implies distinguishedName */
+ filtered_msg->dn = in.dn;
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+
+ /* This does not fail the pidgenhole test */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(filtered_msg->num_elements, 3);
+
+ /* Assert that DB order is preserved */
+ assert_string_equal(filtered_msg->elements[0].name, "bar");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_int_equal(filtered_msg->elements[0].values[0].length,
+ strlen(value1));
+ assert_memory_equal(filtered_msg->elements[0].values[0].data,
+ value1, strlen(value1));
+ assert_string_equal(filtered_msg->elements[1].name, "bar");
+ assert_int_equal(filtered_msg->elements[1].num_values, 1);
+ assert_int_equal(filtered_msg->elements[1].values[0].length,
+ strlen(value2));
+ assert_memory_equal(filtered_msg->elements[1].values[0].data,
+ value2, strlen(value2));
+ /*
+ * assert the ldb_filter_attrs does not modify filtered_msg.dn
+ * in this case
+ */
+ assert_ptr_equal(filtered_msg->dn, in.dn);
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(in.dn));
+}
+
+/*
+ * Test against a record with only one attribute, matching the * in
+ * the list
+ */
+static void test_filter_attrs_one_attr_matched_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ /* Needed as * implies distinguishedName */
+ filtered_msg->dn = in.dn;
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 2);
+
+ /*
+ * assert the ldb_filter_attrs does not modify filtered_msg.dn
+ * in this case
+ */
+ assert_ptr_equal(filtered_msg->dn, in.dn);
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(in.dn));
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "foo",
+ NULL),
+ (const char *)value);
+}
+
+/*
+ * Test against a record with two attributes, matching the * in
+ * the list
+ */
+static void test_filter_attrs_two_attr_matched_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", NULL};
+
+ char value1[] = "The value.......end";
+ char value2[] = "The value..MUST.end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value1,
+ .length = strlen(value1)
+ };
+ struct ldb_val value_2 = {
+ .data = (uint8_t *)value2,
+ .length = strlen(value2)
+ };
+ struct ldb_message_element elements[] = {
+ {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ },
+ {
+ .name = "bar",
+ .num_values = 1,
+ .values = &value_2
+ }
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 2,
+ .elements = elements,
+ };
+
+ assert_non_null(in.dn);
+
+ /* Needed as * implies distinguishedName */
+ filtered_msg->dn = in.dn;
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 3);
+
+ /*
+ * assert the ldb_filter_attrs does not modify filtered_msg.dn
+ * in this case
+ */
+ assert_ptr_equal(filtered_msg->dn, in.dn);
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(in.dn));
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "foo",
+ NULL),
+ (const char *)value1);
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "bar",
+ NULL),
+ (const char *)value2);
+}
+
+/*
+ * Test against a record with only one attribute, matching the * in
+ * the list, but without the DN being pre-filled. Fails due to need
+ * to construct the distinguishedName
+ */
+static void test_filter_attrs_one_attr_matched_star_no_dn(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, -1);
+ assert_null(filtered_msg->elements);
+}
+
+/*
+ * Test against a record with only one attribute, matching the * in
+ * the list plus requsesting distinguishedName
+ */
+static void test_filter_attrs_one_attr_matched_star_dn(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"*", "distinguishedName", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ /* Needed for distinguishedName */
+ filtered_msg->dn = in.dn;
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 2);
+
+ /* show that ldb_filter_attrs does not modify in.dn */
+ assert_ptr_equal(filtered_msg->dn, in.dn);
+
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "distinguishedName",
+ NULL),
+ ldb_dn_get_linearized(in.dn));
+ assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg,
+ "foo",
+ NULL),
+ (const char *)value);
+}
+
+/*
+ * Test against a record with only one attribute, but returning
+ * distinguishedName from the list (only)
+ */
+static void test_filter_attrs_one_attr_matched_dn(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {"distinguishedName", NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ /* Needed for distinguishedName */
+ filtered_msg->dn = in.dn;
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 1);
+
+ /* show that ldb_filter_attrs does not modify in.dn */
+ assert_ptr_equal(filtered_msg->dn, in.dn);
+ assert_string_equal(filtered_msg->elements[0].name, "distinguishedName");
+ assert_int_equal(filtered_msg->elements[0].num_values, 1);
+ assert_string_equal((const char *)filtered_msg->elements[0].values[0].data,
+ ldb_dn_get_linearized(in.dn));
+}
+
+/*
+ * Test against a record with only one attribute, not matching the
+ * empty attribute list
+ */
+static void test_filter_attrs_one_attr_empty_list(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ int ret;
+
+ struct ldb_message *filtered_msg = ldb_msg_new(ctx);
+
+ const char *attrs[] = {NULL};
+
+ char value[] = "The value.......end";
+ struct ldb_val value_1 = {
+ .data = (uint8_t *)value,
+ .length = strlen(value)
+ };
+ struct ldb_message_element element_1 = {
+ .name = "foo",
+ .num_values = 1,
+ .values = &value_1
+ };
+ struct ldb_message in = {
+ .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"),
+ .num_elements = 1,
+ .elements = &element_1,
+ };
+
+ assert_non_null(in.dn);
+
+ ret = ldb_filter_attrs(ctx->ldb,
+ &in,
+ attrs,
+ filtered_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(filtered_msg);
+ assert_int_equal(filtered_msg->num_elements, 0);
+ assert_null(filtered_msg->dn);
+ assert_null(filtered_msg->elements);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_matched,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_matched_of_many,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_attr_matched_attrs,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_attr_matched_one_attr,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_dup_attr_matched_one_attr,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_dup_attr_matched_dup,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_dup_attr_matched_one_of_two,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_dup_attr_matched_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_matched_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_two_attr_matched_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_matched_star_no_dn,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_matched_star_dn,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_matched_dn,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_filter_attrs_one_attr_empty_list,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind b/lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind
new file mode 100644
index 0000000..1747076
--- /dev/null
+++ b/lib/ldb/tests/ldb_key_value_sub_txn_mdb_test.valgrind
@@ -0,0 +1,97 @@
+{
+ Memory allocated by setup
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:setup
+}
+{
+ Memory allocated by setup
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:realloc
+ ...
+ fun:setup
+}
+{
+ Memory allocated by ldb_init
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_init
+}
+{
+ Memory allocated by ldb_init
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:realloc
+ ...
+ fun:ldb_init
+}
+{
+ Memory allocated by noconn_setup
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:noconn_setup
+}
+{
+ Memory allocated by parse, which allocates on the NULL context
+ Memcheck:Leak
+ match-leak-kinds: all
+ fun:malloc
+ ...
+ fun:parse
+}
+{
+ Memory allocated by tdb_parse_data
+ Memcheck:Leak
+ match-leak-kinds: all
+ fun:malloc
+ ...
+ fun:tdb_parse_data
+}
+{
+ Memory allocated by ldb_connect
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_connect
+}
+{
+ Memory allocated by ldb_connect
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:calloc
+ ...
+ fun:ldb_connect
+}
+{
+ Memory allocated by ldb_kv_cache_load
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_kv_cache_load
+}
+{
+ Memory allocated by ldb_kv_index_load
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_kv_index_load
+}
+{
+ Memory allocated by ldb_asprintf_errstring
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_asprintf_errstring
+}
+
diff --git a/lib/ldb/tests/ldb_key_value_sub_txn_test.c b/lib/ldb/tests/ldb_key_value_sub_txn_test.c
new file mode 100644
index 0000000..1eafd2d
--- /dev/null
+++ b/lib/ldb/tests/ldb_key_value_sub_txn_test.c
@@ -0,0 +1,844 @@
+/*
+ * Tests exercising the ldb key value operations.
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+/*
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <limits.h>
+#define NO_FAILURE INT_MAX
+#define FAILURE_LDB_ERR LDB_ERR_OTHER
+
+/*
+ * To test failure in ldb_kv_add, ldb_kv_delete, ldb_kv_modify and ldb_kv_rename
+ * we use the following global variables and macros to trigger a failure in
+ * the ldb_kv_<op>_internal functions. This allows testing of the sub
+ * transaction commits and roll backs in those operations.
+ *
+ * NOTE: Not all back ends support nested/sub transactions
+ */
+int cmocka_unit_test_fail_add_internal_after = NO_FAILURE;
+#define CMOCKA_UNIT_TEST_ADD_INTERNAL_FAIL \
+ {\
+ cmocka_unit_test_fail_add_internal_after--;\
+ if (cmocka_unit_test_fail_add_internal_after <= 0) {\
+ assert_int_equal(LDB_SUCCESS, ret);\
+ ret = FAILURE_LDB_ERR;\
+ }\
+ }\
+
+int cmocka_unit_test_fail_delete_internal_after = NO_FAILURE;
+#define CMOCKA_UNIT_TEST_DELETE_INTERNAL_FAIL \
+ {\
+ cmocka_unit_test_fail_delete_internal_after--;\
+ if (cmocka_unit_test_fail_delete_internal_after <= 0) {\
+ assert_int_equal(LDB_SUCCESS, ret);\
+ ret = FAILURE_LDB_ERR;\
+ }\
+ }\
+
+int cmocka_unit_test_fail_rename_internal_after = NO_FAILURE;
+#define CMOCKA_UNIT_TEST_RENAME_INTERNAL_FAIL \
+ {\
+ cmocka_unit_test_fail_rename_internal_after--;\
+ if (cmocka_unit_test_fail_rename_internal_after <= 0) {\
+ assert_int_equal(LDB_SUCCESS, ret);\
+ ret = FAILURE_LDB_ERR;\
+ }\
+ }\
+
+int cmocka_unit_test_fail_modify_internal_after = NO_FAILURE;
+#define CMOCKA_UNIT_TEST_MODIFY_INTERNAL_FAIL \
+ {\
+ cmocka_unit_test_fail_modify_internal_after--;\
+ if (cmocka_unit_test_fail_modify_internal_after <= 0) {\
+ assert_int_equal(LDB_SUCCESS, ret);\
+ ret = FAILURE_LDB_ERR;\
+ }\
+ }\
+
+#include "ldb_key_value/ldb_kv.c"
+
+
+#define DEFAULT_BE "tdb"
+
+#ifndef TEST_BE
+#define TEST_BE DEFAULT_BE
+#endif /* TEST_BE */
+
+#define NUM_RECS 1024
+
+
+struct test_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+/*
+ * Remove the database files
+ */
+static void unlink_old_db(struct test_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+/*
+ * Test setup
+ */
+static int noconn_setup(void **state)
+{
+ struct test_ctx *test_ctx;
+ cmocka_unit_test_fail_add_internal_after = NO_FAILURE;
+ cmocka_unit_test_fail_delete_internal_after = NO_FAILURE;
+ cmocka_unit_test_fail_rename_internal_after = NO_FAILURE;
+ cmocka_unit_test_fail_modify_internal_after = NO_FAILURE;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "kvopstest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+/*
+ * Test teardown
+ */
+static int noconn_teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+/*
+ * Test setup
+ */
+static int setup(void **state)
+{
+ struct test_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ noconn_setup((void **) &test_ctx);
+
+ 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;
+}
+
+/*
+ * Test teardown
+ */
+static int teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+/*
+ * Build an ldb_kv_context that can be passed to the ldb_kv operation under test
+ */
+static struct ldb_kv_context* build_ldb_kv_context(
+ TALLOC_CTX *ctx,
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+
+ ldb_kv_ctx = talloc_zero(ctx, struct ldb_kv_context);
+ assert_non_null(ldb_kv_ctx);
+
+ ldb_kv_ctx->module = module;
+ ldb_kv_ctx->req = req;
+
+ return ldb_kv_ctx;
+}
+
+/*
+ * Build an add request
+ */
+static struct ldb_request *build_add_request(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char* dc,
+ const char* uuid,
+ const char* cn)
+{
+ int ret;
+ struct ldb_message *msg;
+ struct ldb_request *req;
+
+ msg = ldb_msg_new(ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(msg, ldb, "dc=%s", dc);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", cn);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_msg_add_string(msg, "objectUUID", uuid);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_msg_sanity_check(ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_build_add_req(
+ &req, ldb, ldb, msg, NULL, NULL, ldb_op_default_callback, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ return req;
+}
+
+/*
+ * Build a delete request
+ */
+static struct ldb_request *build_delete_request(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char* dc)
+{
+ int ret = LDB_SUCCESS;
+ struct ldb_dn *dn = NULL;
+ struct ldb_request *req = NULL;
+
+ dn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", dc);
+ assert_non_null(dn);
+
+ ret = ldb_build_del_req(
+ &req, ldb, ctx, dn, NULL, NULL, ldb_op_default_callback, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ return req;
+}
+
+/*
+ * Build a rename request
+ */
+static struct ldb_request *build_rename_request(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char* old_dc,
+ const char* new_dc)
+{
+ int ret = LDB_SUCCESS;
+ struct ldb_dn *old_dn = NULL;
+ struct ldb_dn *new_dn = NULL;
+ struct ldb_request *req = NULL;
+
+ old_dn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", old_dc);
+ assert_non_null(old_dn);
+
+ new_dn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", new_dc);
+ assert_non_null(new_dn);
+
+ ret = ldb_build_rename_req(
+ &req,
+ ldb,
+ ctx,
+ old_dn,
+ new_dn,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ return req;
+}
+
+/*
+ * Build a ldb modify request
+ */
+static struct ldb_request *build_modify_request(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char* dc,
+ const char* cn)
+{
+ int ret;
+ struct ldb_message *msg;
+ struct ldb_request *req;
+
+ msg = ldb_msg_new(ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(msg, ldb, "dc=%s", dc);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_empty(msg, "cn", LDB_FLAG_MOD_REPLACE, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg, "cn", cn);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_msg_sanity_check(ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_build_mod_req(
+ &req, ldb, ldb, msg, NULL, NULL, ldb_op_default_callback, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ return req;
+}
+
+/*
+ * Delete a record from the database
+ */
+static void delete_record(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char* dc)
+{
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+ struct ldb_dn *basedn = NULL;
+ struct ldb_result *result = NULL;
+ struct ldb_request *req = NULL;
+ int ret = LDB_SUCCESS;
+
+ req = build_delete_request(ctx, ldb, dc);
+ ldb_kv_ctx = build_ldb_kv_context(ctx, ldb->modules, req);
+
+ ret = ldb_transaction_start(ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ cmocka_unit_test_fail_delete_internal_after = NO_FAILURE;
+ cmocka_unit_test_fail_modify_internal_after = NO_FAILURE;
+ ret = ldb_kv_delete(ldb_kv_ctx);
+ assert_int_equal(ret, LDB_SUCCESS);
+ TALLOC_FREE(ldb_kv_ctx);
+ TALLOC_FREE(req);
+
+ ret = ldb_transaction_commit(ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Ensure that the record was actually deleted.
+ */
+ basedn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", dc);
+ assert_non_null(basedn);
+
+ /*
+ * DN search, indexed
+ */
+ ret = ldb_search(ldb, ctx, &result, basedn, LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 0);
+ TALLOC_FREE(basedn);
+ TALLOC_FREE(result);
+}
+
+/*
+ * Add a record to the database
+ */
+static void add_record(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char* dc,
+ const char* uuid,
+ const char* cn)
+{
+
+ struct ldb_request *req = NULL;
+ int ret = LDB_SUCCESS;
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+ struct ldb_dn *basedn = NULL;
+ struct ldb_result *result = NULL;
+
+ req = build_add_request(ctx, ldb, dc, uuid, cn);
+
+ ldb_req_set_location(req, "add_record");
+
+ assert_int_equal(ret, LDB_SUCCESS);
+
+
+ ldb_kv_ctx = build_ldb_kv_context(ctx, ldb->modules, req);
+ cmocka_unit_test_fail_add_internal_after = NO_FAILURE;
+ cmocka_unit_test_fail_modify_internal_after = NO_FAILURE;
+
+ ret = ldb_transaction_start(ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv_add(ldb_kv_ctx);
+ assert_int_equal(ret, LDB_SUCCESS);
+ TALLOC_FREE(ldb_kv_ctx);
+ TALLOC_FREE(req);
+
+ ret = ldb_transaction_commit(ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Ensure that the record was actually written.
+ */
+ basedn = ldb_dn_new_fmt(ctx, ldb, "dc=%s", dc);
+ assert_non_null(basedn);
+
+ /*
+ * DN search, indexed
+ */
+ ret = ldb_search(ldb, ctx, &result, basedn, LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ TALLOC_FREE(result);
+
+
+ /*
+ * CN search unindexed
+ */
+ ret = ldb_search(
+ ldb,
+ ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ cn);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ TALLOC_FREE(result);
+ TALLOC_FREE(basedn);
+}
+
+/*
+ * Test that a failed add operation does not change the database.
+ */
+static void test_add_failure(void **state)
+{
+ int ret = LDB_SUCCESS;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_request *req = NULL;
+ struct ldb_dn *basedn = NULL;
+ struct ldb_result *result = NULL;
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ req = build_add_request(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_add_failure",
+ "0123456789abcdef",
+ "test_add_failure_value");
+
+ ldb_req_set_location(req, "test_add_failure");
+
+ ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req);
+ cmocka_unit_test_fail_add_internal_after = 1;
+
+ ret = ldb_transaction_start(test_ctx->ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv_add(ldb_kv_ctx);
+
+ assert_int_equal(ret, FAILURE_LDB_ERR);
+ TALLOC_FREE(ldb_kv_ctx);
+ TALLOC_FREE(req);
+
+
+ /*
+ * a search for "cn=test_add_failure_value" should fail
+ * as the transaction containing the operation should have been
+ * rolled back leaving the database consistent
+ *
+ * This should be an un-indexed search so the index caches won't be
+ * used.
+ */
+ basedn = ldb_dn_new_fmt(
+ tmp_ctx,
+ test_ctx->ldb,
+ "dc=%s",
+ "test_add_failure");
+ assert_non_null(basedn);
+
+ ret = ldb_search(
+ test_ctx->ldb, tmp_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ "test_add_failure_value");
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 0);
+ TALLOC_FREE(basedn);
+ TALLOC_FREE(result);
+
+ ldb_transaction_cancel(test_ctx->ldb);
+ TALLOC_FREE(tmp_ctx);
+}
+
+
+/*
+ * Test that a failed delete operation does not modify the database.
+ */
+static void test_delete_failure(void **state)
+{
+ int ret = LDB_SUCCESS;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_request *req = NULL;
+ struct ldb_dn *basedn = NULL;
+ struct ldb_result *result = NULL;
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ add_record(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_delete_failure",
+ "0123456789abcded",
+ "test_delete_failure_value");
+
+ req = build_delete_request(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_delete_failure");
+
+ ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req);
+ cmocka_unit_test_fail_delete_internal_after = 1;
+
+ ret = ldb_transaction_start(test_ctx->ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv_delete(ldb_kv_ctx);
+ assert_int_equal(ret, FAILURE_LDB_ERR);
+ TALLOC_FREE(ldb_kv_ctx);
+ TALLOC_FREE(req);
+
+ /*
+ * a search for "cn=test_add_failure_value" should succeed
+ * as the transaction containing the operation should have been
+ * rolled back leaving the database consistent
+ *
+ * This should be an un-indexed search so the index caches won't be
+ * used.
+ */
+ basedn = ldb_dn_new_fmt(
+ tmp_ctx,
+ test_ctx->ldb,
+ "dc=%s",
+ "test_delete_failure");
+ assert_non_null(basedn);
+
+ ret = ldb_search(
+ test_ctx->ldb, tmp_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ "test_delete_failure_value");
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ TALLOC_FREE(basedn);
+ TALLOC_FREE(result);
+
+
+ ldb_transaction_cancel(test_ctx->ldb);
+ delete_record(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_delete_failure");
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Test that a failed rename operation dies not change the database
+ */
+static void test_rename_failure(void **state)
+{
+ int ret = LDB_SUCCESS;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_request *req = NULL;
+ struct ldb_dn *basedn = NULL;
+ struct ldb_result *result = NULL;
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ add_record(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_rename_failure",
+ "0123456789abcdec",
+ "test_rename_failure_value");
+
+ req = build_rename_request(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_rename_failure",
+ "test_rename_failure_renamed");
+
+ ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req);
+ cmocka_unit_test_fail_rename_internal_after = 1;
+
+ ret = ldb_transaction_start(test_ctx->ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv_rename(ldb_kv_ctx);
+ assert_int_equal(ret, FAILURE_LDB_ERR);
+ TALLOC_FREE(ldb_kv_ctx);
+ TALLOC_FREE(req);
+
+ /*
+ * The original record should be present
+ */
+ basedn = ldb_dn_new_fmt(
+ tmp_ctx,
+ test_ctx->ldb,
+ "dc=%s",
+ "test_rename_failure");
+ assert_non_null(basedn);
+
+ ret = ldb_search(
+ test_ctx->ldb, tmp_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ "test_rename_failure_value");
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ TALLOC_FREE(basedn);
+ TALLOC_FREE(result);
+
+ /*
+ * And the renamed record should not be present
+ */
+ basedn = ldb_dn_new_fmt(
+ tmp_ctx,
+ test_ctx->ldb,
+ "dc=%s",
+ "test_rename_failure_renamed");
+ assert_non_null(basedn);
+
+ ret = ldb_search(
+ test_ctx->ldb, tmp_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ "test_rename_failure_value");
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 0);
+ TALLOC_FREE(basedn);
+ TALLOC_FREE(result);
+
+ ldb_transaction_cancel(test_ctx->ldb);
+ delete_record(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_rename_failure");
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Test that a failed modification operation does not change the database
+ */
+static void test_modify_failure(void **state)
+{
+ int ret = LDB_SUCCESS;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_request *req = NULL;
+ struct ldb_dn *basedn = NULL;
+ struct ldb_result *result = NULL;
+ struct ldb_kv_context *ldb_kv_ctx = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ add_record(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_modify_failure",
+ "0123456789abcdeb",
+ "test_modify_failure_value");
+
+ req = build_modify_request(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_modify_failure",
+ "test_modify_failure_value_modified");
+
+ ldb_kv_ctx = build_ldb_kv_context(tmp_ctx, test_ctx->ldb->modules, req);
+ cmocka_unit_test_fail_modify_internal_after = 2;
+
+ ret = ldb_transaction_start(test_ctx->ldb);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv_modify(ldb_kv_ctx);
+ assert_int_equal(ret, FAILURE_LDB_ERR);
+ TALLOC_FREE(ldb_kv_ctx);
+ TALLOC_FREE(req);
+
+
+ /*
+ * The original value should be present
+ */
+ basedn = ldb_dn_new_fmt(
+ tmp_ctx,
+ test_ctx->ldb,
+ "dc=%s",
+ "test_modify_failure");
+ assert_non_null(basedn);
+
+ ret = ldb_search(
+ test_ctx->ldb, tmp_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ "test_modify_failure_value");
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ TALLOC_FREE(result);
+
+ /*
+ * And the modified record should not be present
+ */
+ ret = ldb_search(
+ test_ctx->ldb, tmp_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ NULL,
+ "(cn=%s)",
+ "test_modify_failure_value_modified");
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 0);
+ TALLOC_FREE(basedn);
+ TALLOC_FREE(result);
+
+ ldb_transaction_cancel(test_ctx->ldb);
+ delete_record(
+ tmp_ctx,
+ test_ctx->ldb,
+ "test_modify_failure");
+ TALLOC_FREE(tmp_ctx);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_add_failure,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete_failure,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_rename_failure,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_modify_failure,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_key_value_test.c b/lib/ldb/tests/ldb_key_value_test.c
new file mode 100644
index 0000000..97f717b
--- /dev/null
+++ b/lib/ldb/tests/ldb_key_value_test.c
@@ -0,0 +1,388 @@
+/*
+ * Tests exercising the ldb key value operations.
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+/*
+ *
+ * Tests for the ldb key value layer
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "ldb_key_value/ldb_kv.c"
+#include "ldb_key_value/ldb_kv_index.c"
+#include "ldb_key_value/ldb_kv_search.c"
+
+#define DEFAULT_BE "tdb"
+
+#ifndef TEST_BE
+#define TEST_BE DEFAULT_BE
+#endif /* TEST_BE */
+
+#define NUM_RECS 1024
+int ldb_kv_cache_reload(struct ldb_module *module) {
+ return LDB_SUCCESS;
+}
+int ldb_kv_cache_load(struct ldb_module *module) {
+ return LDB_SUCCESS;
+}
+int ldb_kv_check_at_attributes_values(const struct ldb_val *value) {
+ return LDB_SUCCESS;
+}
+int ldb_kv_increase_sequence_number(struct ldb_module *module) {
+ return LDB_SUCCESS;
+}
+
+struct test_ctx { uint8_t dummy; };
+
+static int setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ talloc_free(test_ctx);
+ return 0;
+}
+
+/*
+ * Test that the index cache is opened by ldb_kv_index_transaction_start
+ * and correctly initialised with the passed index cache size.
+ */
+static void test_index_cache_init(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ int ret = LDB_SUCCESS;
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+ ldb_module_set_private(module, ldb_kv);
+
+ ret = ldb_kv_index_transaction_start(module, 191);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_non_null(ldb_kv->idxptr);
+ assert_non_null(ldb_kv->idxptr->itdb);
+ assert_int_equal(191, tdb_hash_size(ldb_kv->idxptr->itdb));
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+}
+
+static int mock_begin_write(struct ldb_kv_private* ldb_kv) {
+ return LDB_SUCCESS;
+}
+static int mock_abort_write(struct ldb_kv_private* ldb_kv) {
+ return LDB_SUCCESS;
+}
+
+/*
+ * Test that the index cache is set to the default cache size at the start of
+ * a transaction.
+ */
+static void test_default_index_cache_size(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ int ret = LDB_SUCCESS;
+ const struct kv_db_ops ops = {
+ .begin_write = mock_begin_write,
+ .abort_write = mock_abort_write
+ };
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+ ldb_kv->pid = getpid();
+ ldb_kv->kv_ops = &ops;
+ ldb_kv->index_transaction_cache_size = DEFAULT_INDEX_CACHE_SIZE;
+ ldb_module_set_private(module, ldb_kv);
+
+ ret = ldb_kv_start_trans(module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal(
+ DEFAULT_INDEX_CACHE_SIZE,
+ tdb_hash_size(ldb_kv->idxptr->itdb));
+
+ ret = ldb_kv_del_trans(module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+}
+
+static int db_size = 0;
+static size_t mock_get_size(struct ldb_kv_private *ldb_kv) {
+ return db_size;
+}
+
+static int mock_iterate(
+ struct ldb_kv_private *ldb_kv,
+ ldb_kv_traverse_fn fn,
+ void *ctx) {
+ return 1;
+}
+
+/*
+ * Test that the index cache is correctly sized by the re_index call
+ */
+static void test_reindex_cache_size(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ int ret = LDB_SUCCESS;
+ const struct kv_db_ops ops = {
+ .iterate = mock_iterate,
+ .get_size = mock_get_size,
+ };
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+ ldb_kv->kv_ops = &ops;
+ ldb_module_set_private(module, ldb_kv);
+
+ /*
+ * Use a value less than the DEFAULT_INDEX_CACHE_SIZE
+ * Should get the DEFAULT_INDEX_CACHE_SIZE
+ */
+ db_size = DEFAULT_INDEX_CACHE_SIZE - 1;
+ ret = ldb_kv_reindex(module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal(
+ DEFAULT_INDEX_CACHE_SIZE,
+ tdb_hash_size(ldb_kv->idxptr->itdb));
+
+ /*
+ * Use a value greater than the DEFAULT_INDEX_CACHE_SIZE
+ * Should get the value specified.
+ */
+ db_size = DEFAULT_INDEX_CACHE_SIZE + 1;
+ ret = ldb_kv_reindex(module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal(db_size, tdb_hash_size(ldb_kv->idxptr->itdb));
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+}
+
+/*
+ * Test that ldb_kv_init_store sets the default index transaction cache size
+ * if the option is not supplied.
+ */
+static void test_init_store_default_index_cache_size(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret = LDB_SUCCESS;
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb = talloc_zero(test_ctx, struct ldb_context);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+
+ ret = ldb_kv_init_store(ldb_kv, "test", ldb, NULL, &module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal(
+ DEFAULT_INDEX_CACHE_SIZE,
+ ldb_kv->index_transaction_cache_size);
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+ TALLOC_FREE(ldb);
+}
+
+/*
+ * Test that ldb_kv_init_store sets the index transaction cache size
+ * to the value specified in the option.
+ */
+static void test_init_store_set_index_cache_size(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ struct ldb_context *ldb = NULL;
+ const char *options[] = {"transaction_index_cache_size:1900", NULL};
+ int ret = LDB_SUCCESS;
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb = talloc_zero(test_ctx, struct ldb_context);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+
+ ret = ldb_kv_init_store(ldb_kv, "test", ldb, options, &module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal( 1900, ldb_kv->index_transaction_cache_size);
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+ TALLOC_FREE(ldb);
+}
+
+/*
+ * Test that ldb_kv_init_store sets the default index transaction cache size
+ * if the value specified in the option is not a number.
+ */
+static void test_init_store_set_index_cache_size_non_numeric(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ struct ldb_context *ldb = NULL;
+ const char *options[] = {"transaction_index_cache_size:fred", NULL};
+ int ret = LDB_SUCCESS;
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb = talloc_zero(test_ctx, struct ldb_context);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+
+ ret = ldb_kv_init_store(ldb_kv, "test", ldb, options, &module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal(
+ DEFAULT_INDEX_CACHE_SIZE,
+ ldb_kv->index_transaction_cache_size);
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+ TALLOC_FREE(ldb);
+}
+
+/*
+ * Test that ldb_kv_init_store sets the default index transaction cache size
+ * if the value specified is too large
+ */
+static void test_init_store_set_index_cache_size_range(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(
+ *state,
+ struct test_ctx);
+ struct ldb_module *module = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ struct ldb_context *ldb = NULL;
+ const char *options[] = {
+ "transaction_index_cache_size:0xfffffffffffffffffffffffffffff",
+ NULL};
+ int ret = LDB_SUCCESS;
+
+ module = talloc_zero(test_ctx, struct ldb_module);
+ ldb = talloc_zero(test_ctx, struct ldb_context);
+ ldb_kv = talloc_zero(test_ctx, struct ldb_kv_private);
+
+ ret = ldb_kv_init_store(ldb_kv, "test", ldb, options, &module);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ assert_int_equal(
+ DEFAULT_INDEX_CACHE_SIZE,
+ ldb_kv->index_transaction_cache_size);
+
+ TALLOC_FREE(ldb_kv);
+ TALLOC_FREE(module);
+ TALLOC_FREE(ldb);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_index_cache_init,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_default_index_cache_size,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_reindex_cache_size,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_init_store_default_index_cache_size,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_init_store_set_index_cache_size,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_init_store_set_index_cache_size_non_numeric,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_init_store_set_index_cache_size_range,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_kv_ops_test.c b/lib/ldb/tests/ldb_kv_ops_test.c
new file mode 100644
index 0000000..b84ed0c
--- /dev/null
+++ b/lib/ldb/tests/ldb_kv_ops_test.c
@@ -0,0 +1,1819 @@
+/*
+ * Tests exercising the ldb key value operations.
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+/*
+ * A KV module is expected to have the following behaviour
+ *
+ * - A transaction must be open to perform any read, write or delete operation
+ * - Writes and Deletes should not be visible until a transaction is committed
+ * - Nested transactions are not permitted
+ * - transactions can be rolled back and committed.
+ * - supports iteration over all records in the database
+ * - supports the update_in_iterate operation allowing entries to be
+ * re-keyed.
+ * - has a get_size implementation that returns an estimate of the number of
+ * records in the database. Note that this can be an estimate rather than
+ * an accurate size.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "ldb_tdb/ldb_tdb.h"
+#include "ldb_key_value/ldb_kv.h"
+
+
+#define DEFAULT_BE "tdb"
+
+#ifndef TEST_BE
+#define TEST_BE DEFAULT_BE
+#endif /* TEST_BE */
+
+#define NUM_RECS 1024
+
+
+struct test_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct test_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int noconn_setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "kvopstest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int noconn_teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int setup(void **state)
+{
+ struct test_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ noconn_setup((void **) &test_ctx);
+
+ 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;
+}
+
+static int teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static struct ldb_kv_private *get_ldb_kv(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ldb_kv = talloc_get_type(data, struct ldb_kv_private);
+ assert_non_null(ldb_kv);
+
+ return ldb_kv;
+}
+
+static int parse(struct ldb_val key,
+ struct ldb_val data,
+ void *private_data)
+{
+ struct ldb_val* read = private_data;
+
+ /* Yes, we leak this. That is OK */
+ read->data = talloc_size(NULL,
+ data.length);
+ assert_non_null(read->data);
+
+ memcpy(read->data, data.data, data.length);
+ read->length = data.length;
+ return LDB_SUCCESS;
+}
+
+/*
+ * Parse function that just returns the int we pass it.
+ */
+static int parse_return(struct ldb_val key,
+ struct ldb_val data,
+ void *private_data)
+{
+ int *rcode = private_data;
+ return *rcode;
+}
+
+/*
+ * Test that data can be written to the kv store and be read back.
+ */
+static void test_add_get(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+ int rcode;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+
+ /*
+ * Now check that the error code we return in the
+ * parse function is returned by fetch_and_parse.
+ */
+ for (rcode=0; rcode<50; rcode++) {
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key,
+ parse_return,
+ &rcode);
+ assert_int_equal(ret, rcode);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test that attempts to read data without a read transaction fail.
+ */
+static void test_read_outside_transaction(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ * Note there is no read transaction active
+ */
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test that data can be deleted from the kv store
+ */
+static void test_delete(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now delete it.
+ */
+ ret = ldb_kv->kv_ops->delete (ldb_kv, key);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now try to read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Check that writes are correctly rolled back when a transaction
+ * is rolled back.
+ */
+static void test_transaction_abort_write(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+
+
+ /*
+ * Now abort the transaction
+ */
+ ret = ldb_kv->kv_ops->abort_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back, should not be there
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Check that deletes are correctly rolled back when a transaction is
+ * aborted.
+ */
+static void test_transaction_abort_delete(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now delete it.
+ */
+ ret = ldb_kv->kv_ops->delete (ldb_kv, key);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+
+ /*
+ * Abort the transaction
+ */
+ ret = ldb_kv->kv_ops->abort_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now try to read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test that writes outside a transaction fail
+ */
+static void test_write_outside_transaction(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Attempt to write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test data can not be deleted outside a transaction
+ */
+static void test_delete_outside_transaction(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now attempt to delete a record
+ */
+ ret = ldb_kv->kv_ops->delete (ldb_kv, key);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ /*
+ * And now read it back
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static int traverse_fn(struct ldb_kv_private *ldb_kv,
+ struct ldb_val key,
+ struct ldb_val data,
+ void *ctx)
+{
+
+ int *visits = ctx;
+ int i;
+
+ if (strncmp("key ", (char *) key.data, 4) == 0) {
+ i = strtol((char *) &key.data[4], NULL, 10);
+ visits[i]++;
+ }
+ return LDB_SUCCESS;
+}
+
+/*
+ * Test that iterate visits all the records.
+ */
+static void test_iterate(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ int i;
+ int num_recs = 1024;
+ int visits[num_recs];
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the records
+ */
+ for (i = 0; i < num_recs; i++) {
+ struct ldb_val key;
+ struct ldb_val rec;
+ int flags = 0;
+
+ visits[i] = 0;
+ key.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", i);
+ key.length = strlen((char *)key.data) + 1;
+
+ rec.data = (uint8_t *) talloc_asprintf(tmp_ctx,
+ "data for record (%04d)",
+ i);
+ rec.length = strlen((char *)rec.data) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, rec, flags);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(key.data);
+ TALLOC_FREE(rec.data);
+ }
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now iterate over the kv store and ensure that all the
+ * records are visited.
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->iterate(ldb_kv, traverse_fn, visits);
+ for (i = 0; i <num_recs; i++) {
+ assert_int_equal(1, visits[i]);
+ }
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+static void do_iterate_range_test(void **state, int range_start,
+ int range_end, bool fail)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = NULL;
+ int i;
+ int num_recs = 1024;
+ int skip_recs = 10;
+ int visits[num_recs];
+ struct ldb_val sk, ek;
+
+ TALLOC_CTX *tmp_ctx;
+
+ ldb_kv = get_ldb_kv(test_ctx->ldb);
+ assert_non_null(ldb_kv);
+
+ for (i = 0; i < num_recs; i++){
+ visits[i] = 0;
+ }
+
+ /*
+ * No iterate_range on tdb
+ */
+ if (strcmp(TEST_BE, "tdb") == 0) {
+ return;
+ }
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the records
+ */
+ for (i = skip_recs; i <= num_recs - skip_recs; i++) {
+ struct ldb_val key;
+ struct ldb_val rec;
+ int flags = 0;
+
+ key.data = (uint8_t *)talloc_asprintf(tmp_ctx,
+ "key %04d",
+ i);
+ key.length = strlen((char *)key.data);
+
+ rec.data = (uint8_t *)talloc_asprintf(tmp_ctx,
+ "data for record (%04d)",
+ i);
+ rec.length = strlen((char *)rec.data) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, rec, flags);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(key.data);
+ TALLOC_FREE(rec.data);
+ }
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ sk.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", range_start);
+ sk.length = strlen((char *)sk.data);
+
+ ek.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", range_end);
+ ek.length = strlen((char *)ek.data) + 1;
+
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->iterate_range(ldb_kv, sk, ek,
+ traverse_fn, visits);
+ if (fail){
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+ TALLOC_FREE(tmp_ctx);
+ return;
+ } else{
+ assert_int_equal(ret, 0);
+ }
+ for (i = 0; i < num_recs; i++) {
+ if (i >= skip_recs && i <= num_recs - skip_recs &&
+ i >= range_start && i <= range_end){
+ assert_int_equal(1, visits[i]);
+ } else {
+ assert_int_equal(0, visits[i]);
+ }
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Test that iterate_range visits all the records between two keys.
+ */
+static void test_iterate_range(void **state)
+{
+ do_iterate_range_test(state, 300, 900, false);
+
+ /*
+ * test start_key = end_key
+ */
+ do_iterate_range_test(state, 20, 20, false);
+
+ /*
+ * test reverse range fails
+ */
+ do_iterate_range_test(state, 50, 40, true);
+
+ /*
+ * keys are between 10-1014 so test with keys outside that range
+ */
+ do_iterate_range_test(state, 0, 20, false);
+ do_iterate_range_test(state, 1010, 1030, false);
+ do_iterate_range_test(state, 0, 1030, false);
+}
+
+struct update_context {
+ struct ldb_context* ldb;
+ int visits[NUM_RECS];
+};
+
+static int update_fn(struct ldb_kv_private *ldb_kv,
+ struct ldb_val key,
+ struct ldb_val data,
+ void *ctx)
+{
+
+ struct ldb_val new_key;
+ struct ldb_module *module = NULL;
+ struct update_context *context =NULL;
+ int ret = LDB_SUCCESS;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(ldb_kv);
+ assert_non_null(tmp_ctx);
+
+ context = talloc_get_type_abort(ctx, struct update_context);
+
+ module = talloc_zero(tmp_ctx, struct ldb_module);
+ module->ldb = context->ldb;
+
+ if (strncmp("key ", (char *) key.data, 4) == 0) {
+ int i = strtol((char *) &key.data[4], NULL, 10);
+ context->visits[i]++;
+ new_key.data = talloc_memdup(tmp_ctx, key.data, key.length);
+ new_key.length = key.length;
+ new_key.data[0] = 'K';
+
+ ret = ldb_kv->kv_ops->update_in_iterate(
+ ldb_kv, key, new_key, data, &module);
+ }
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/*
+ * Test that update_in_iterate behaves as expected.
+ */
+static void test_update_in_iterate(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ int i;
+ struct update_context *context = NULL;
+
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ context = talloc_zero(tmp_ctx, struct update_context);
+ assert_non_null(context);
+ context->ldb = test_ctx->ldb;
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the records
+ */
+ for (i = 0; i < NUM_RECS; i++) {
+ struct ldb_val key;
+ struct ldb_val rec;
+ int flags = 0;
+
+ key.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", i);
+ key.length = strlen((char *)key.data) + 1;
+
+ rec.data = (uint8_t *) talloc_asprintf(tmp_ctx,
+ "data for record (%04d)",
+ i);
+ rec.length = strlen((char *)rec.data) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, rec, flags);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(key.data);
+ TALLOC_FREE(rec.data);
+ }
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now iterate over the kv store and ensure that all the
+ * records are visited.
+ */
+
+ /*
+ * Needs to be done inside a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_kv->kv_ops->iterate(ldb_kv, update_fn, context);
+ for (i = 0; i < NUM_RECS; i++) {
+ assert_int_equal(1, context->visits[i]);
+ }
+
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Ensure that writes are not visible until the transaction has been
+ * committed.
+ */
+static void test_write_transaction_isolation(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+ const char *VAL1 = "VALUE01";
+
+ const char *KEY2 = "KEY02";
+ const char *VAL2 = "VALUE02";
+
+ /*
+ * Pipes etc to coordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid, w_pid;
+ int wstatus;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+
+ /*
+ * Add a record to the database
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL1);
+ val.length = strlen(VAL1) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the transaction to start
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ldb_kv = get_ldb_kv(ldb);
+
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY2 is not there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2 + 1);
+
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Signal the other process to commit the transaction
+ */
+ ret = write(to_parent[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__": write returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Wait for the transaction to be committed
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+
+ /*
+ * Check that KEY2 is there
+ */
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL2) + 1) != val.length) {
+ print_error(__location__": KEY2 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL2) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL2, val.data, strlen(VAL2)) != 0) {
+ print_error(__location__": KEY2 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL2,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ exit(0);
+ }
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Begin a transaction and add a record to the database
+ * but leave the transaction open
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL2);
+ val.length = strlen(VAL2) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * Wait for the child process to check the DB state while the
+ * transaction is active
+ */
+ ret = read(to_parent[0], buf, 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(0, ret);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ w_pid = waitpid(pid, &wstatus, 0);
+ assert_int_equal(pid, w_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Ensure that deletes are not visible until the transaction has been
+ * committed.
+ */
+static void test_delete_transaction_isolation(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+ const char *VAL1 = "VALUE01";
+
+ const char *KEY2 = "KEY02";
+ const char *VAL2 = "VALUE02";
+
+ /*
+ * Pipes etc to coordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid, w_pid;
+ int wstatus;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+
+ /*
+ * Add records to the database
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL1);
+ val.length = strlen(VAL1) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL2);
+ val.length = strlen(VAL2) + 1;
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the transaction to be started
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ldb_kv = get_ldb_kv(ldb);
+
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY2 is there
+ */
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL2) + 1) != val.length) {
+ print_error(__location__": KEY2 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL2) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL2, val.data, strlen(VAL2)) != 0) {
+ print_error(__location__": KEY2 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL2,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Signal the other process to commit the transaction
+ */
+ ret = write(to_parent[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__": write returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Wait for the transaction to be committed
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY2 is not there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2 + 1);
+
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ TALLOC_FREE(tmp_ctx);
+ exit(0);
+ }
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Begin a transaction and delete a record from the database
+ * but leave the transaction open
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ ret = ldb_kv->kv_ops->delete (ldb_kv, key);
+ assert_int_equal(ret, 0);
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * Wait for the child process to check the DB state while the
+ * transaction is active
+ */
+ ret = read(to_parent[0], buf, 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(0, ret);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ w_pid = waitpid(pid, &wstatus, 0);
+ assert_int_equal(pid, w_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+
+/*
+ * Test that get_size returns a sensible estimate of the number of records
+ * in the database.
+ */
+static void test_get_size(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+ size_t size = 0;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ size = ldb_kv->kv_ops->get_size(ldb_kv);
+#if defined(TEST_LMDB)
+ assert_int_equal(2, size);
+#else
+ /*
+ * The tdb implementation of get_size over estimates for sparse files
+ * which is perfectly acceptable for it's intended use.
+ * mipsel, ia64: 9994
+ * ppc64el, powerpc, ppc64: 13369
+ * sparc64: 5046
+ */
+ assert_in_range(size, 2500, 15000);
+#endif
+
+ /*
+ * Begin a transaction
+ */
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, 0);
+
+ size = ldb_kv->kv_ops->get_size(ldb_kv);
+#ifdef TEST_LMDB
+ assert_int_equal(3, size);
+#else
+ /*
+ * The tdb implementation of get_size over estimates for sparse files
+ * which is perfectly acceptable for it's intended use.
+ * mipsel, ia64: 9994
+ * ppc64el, powerpc, ppc64: 13369
+ * sparc64: 5046
+ */
+ assert_in_range(size, 2500, 15000);
+#endif
+ talloc_free(tmp_ctx);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_add_get,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_transaction_abort_write,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_transaction_abort_delete,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_read_outside_transaction,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_write_outside_transaction,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete_outside_transaction,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_iterate,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_iterate_range,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_in_iterate,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_write_transaction_isolation,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete_transaction_isolation,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_get_size,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_kv_ops_test.valgrind b/lib/ldb/tests/ldb_kv_ops_test.valgrind
new file mode 100644
index 0000000..1747076
--- /dev/null
+++ b/lib/ldb/tests/ldb_kv_ops_test.valgrind
@@ -0,0 +1,97 @@
+{
+ Memory allocated by setup
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:setup
+}
+{
+ Memory allocated by setup
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:realloc
+ ...
+ fun:setup
+}
+{
+ Memory allocated by ldb_init
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_init
+}
+{
+ Memory allocated by ldb_init
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:realloc
+ ...
+ fun:ldb_init
+}
+{
+ Memory allocated by noconn_setup
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:noconn_setup
+}
+{
+ Memory allocated by parse, which allocates on the NULL context
+ Memcheck:Leak
+ match-leak-kinds: all
+ fun:malloc
+ ...
+ fun:parse
+}
+{
+ Memory allocated by tdb_parse_data
+ Memcheck:Leak
+ match-leak-kinds: all
+ fun:malloc
+ ...
+ fun:tdb_parse_data
+}
+{
+ Memory allocated by ldb_connect
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_connect
+}
+{
+ Memory allocated by ldb_connect
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:calloc
+ ...
+ fun:ldb_connect
+}
+{
+ Memory allocated by ldb_kv_cache_load
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_kv_cache_load
+}
+{
+ Memory allocated by ldb_kv_index_load
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_kv_index_load
+}
+{
+ Memory allocated by ldb_asprintf_errstring
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_asprintf_errstring
+}
+
diff --git a/lib/ldb/tests/ldb_lmdb_free_list_test.c b/lib/ldb/tests/ldb_lmdb_free_list_test.c
new file mode 100644
index 0000000..246fdc7
--- /dev/null
+++ b/lib/ldb/tests/ldb_lmdb_free_list_test.c
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) Catalyst.Net Ltd 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * Tests confirming lmdb's handling of the free space list in the presence
+ * of active and stale readers. A stale reader is a process that opens a
+ * read lock and then exits without releasing the lock.
+ *
+ * lmdb uses MVCC to maintain databased consistency, new copies of updated
+ * records are written to the database. The old entries are only
+ * reused when they are no longer referenced in a read transaction.
+ *
+ * The tests all update a single record multiple times
+ *
+ * If there is a read transaction or a stale reader lmdb will report
+ * out of space.
+ *
+ * If no read transaction and no stale reader, lmdb reclaims space from the
+ * free list.
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "ldb_tdb/ldb_tdb.h"
+#include "ldb_key_value/ldb_kv.h"
+
+#define DEFAULT_BE "mdb"
+
+#ifndef TEST_BE
+#define TEST_BE DEFAULT_BE
+#endif /* TEST_BE */
+
+const int RECORD_SIZE = 6144;
+const int ITERATIONS = 192;
+
+struct test_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct test_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int noconn_setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "lmdb_free_list_test.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile =
+ talloc_asprintf(test_ctx, "%s-lock", test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath =
+ talloc_asprintf(test_ctx, TEST_BE "://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int noconn_teardown(void **state)
+{
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int setup(void **state)
+{
+ struct test_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+ /*
+ * Use a 1MiB DB for this test
+ */
+ const char *options[] = {"lmdb_env_size:1048576", NULL};
+
+ noconn_setup((void **)&test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, options);
+ 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;
+}
+
+static int teardown(void **state)
+{
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ noconn_teardown((void **)&test_ctx);
+ return 0;
+}
+
+static struct ldb_kv_private *get_ldb_kv(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ldb_kv = talloc_get_type(data, struct ldb_kv_private);
+ assert_non_null(ldb_kv);
+
+ return ldb_kv;
+}
+
+static int parse(struct ldb_val key, struct ldb_val data, void *private_data)
+{
+ struct ldb_val *read = private_data;
+
+ /* Yes, we leak this. That is OK */
+ read->data = talloc_size(NULL, data.length);
+ assert_non_null(read->data);
+
+ memcpy(read->data, data.data, data.length);
+ read->length = data.length;
+ return LDB_SUCCESS;
+}
+
+/*
+ * This test has the same structure as the test_free_list_read_lock
+ * except the parent process does not keep the read lock open while the
+ * child process is performing an update.
+ */
+static void test_free_list_no_read_lock(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+
+ /*
+ * Pipes etc to coordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid;
+ size_t i;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+ /*
+ * Child process
+ */
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the parent to get ready.
+ */
+ ret = read(to_child[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(ldb);
+
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ldb_kv = get_ldb_kv(ldb);
+ assert_non_null(ldb_kv);
+ /*
+ * Add a record to the database
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+ val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE);
+ assert_non_null(val.data);
+ memset(val.data, 'x', RECORD_SIZE);
+ val.length = RECORD_SIZE;
+ /*
+ * Do more iterations than when a read lock, stale reader
+ * active to confirm that the space is being re-used.
+ */
+ for (i = 0; i < ITERATIONS * 10; i++) {
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+
+ /*
+ * Signal the parent that we've done the updates
+ */
+ ret = write(to_parent[1], "GO", 2);
+ assert_int_equal(ret, 2);
+ exit(0);
+ }
+
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Begin a read transaction
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Now close it
+ */
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * Wait for the child process to update the record
+ */
+ ret = read(to_parent[0], buf, 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * Begin a read transaction
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+ /*
+ * read the record
+ * and close the transaction
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ close(to_child[1]);
+ close(to_parent[0]);
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * This test has the same structure as the test_free_list_read_lock
+ * except the parent process keeps the read lock open while the
+ * child process is performing an update.
+ */
+static void test_free_list_read_lock(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+
+ /*
+ * Pipes etc to coordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid;
+ size_t i;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+ /*
+ * Child process
+ */
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the transaction to start
+ */
+ ret = read(to_child[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(ldb);
+
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ldb_kv = get_ldb_kv(ldb);
+ assert_non_null(ldb_kv);
+ /*
+ * Add a record to the database
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+ val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE);
+ assert_non_null(val.data);
+ memset(val.data, 'x', RECORD_SIZE);
+ val.length = RECORD_SIZE;
+ for (i = 0; i < ITERATIONS; i++) {
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, 0);
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ if (ret == LDB_ERR_BUSY && i > 0) {
+ int rc = ldb_kv->kv_ops->abort_write(ldb_kv);
+ assert_int_equal(rc, LDB_SUCCESS);
+ break;
+ }
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+ assert_int_equal(ret, LDB_ERR_BUSY);
+ assert_int_not_equal(i, 0);
+
+ /*
+ * Begin a read transaction
+ */
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+ /*
+ * read the record
+ * and close the transaction
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Signal the the parent that we've done the update
+ */
+ ret = write(to_parent[1], "GO", 2);
+ assert_int_equal(ret, 2);
+ exit(0);
+ }
+
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Begin a read transaction
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(ret, 2);
+
+ /*
+ * Wait for the child process to update the record
+ */
+ ret = read(to_parent[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ /*
+ * read the record
+ * and close the transaction
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ close(to_child[1]);
+ close(to_parent[0]);
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * This tests forks a child process that opens a read lock and then
+ * exits. This results in a stale reader entry in the lmdb lock file.
+ */
+static void test_free_list_stale_reader(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+
+ /*
+ * Pipes etc to coordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid;
+ size_t i;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+ /*
+ * Child process
+ */
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the parent to get ready
+ */
+ ret = read(to_child[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(ldb);
+
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ldb_kv = get_ldb_kv(ldb);
+ assert_non_null(ldb_kv);
+
+ /*
+ * Begin a read transaction
+ */
+ ret = ldb_kv->kv_ops->lock_read(ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Now exit with out releasing the read lock
+ * this will result in a stale entry in the
+ * read lock table.
+ */
+
+ exit(0);
+ }
+
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Tell the child to start
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(ret, 2);
+
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Now wait for the child process to complete
+ */
+ waitpid(pid, NULL, 0);
+
+ /*
+ * Add a record to the database
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+ val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE);
+ assert_non_null(val.data);
+ memset(val.data, 'x', RECORD_SIZE);
+ val.length = RECORD_SIZE;
+ for (i = 0; i < ITERATIONS; i++) {
+ ret = ldb_kv->kv_ops->begin_write(ldb_kv);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
+ if (ret == LDB_ERR_BUSY && i > 0) {
+ int rc = ldb_kv->kv_ops->abort_write(ldb_kv);
+ assert_int_equal(rc, LDB_SUCCESS);
+ break;
+ }
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->finish_write(ldb_kv);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+ /*
+ * We now do an explicit clear of stale readers at the start of a
+ * write transaction so should not get LDB_ERR_BUSY any more
+ * assert_int_equal(ret, LDB_ERR_BUSY);
+ */
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_not_equal(i, 0);
+
+ /*
+ * Begin a read transaction
+ */
+ ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+ /*
+ * read the record
+ * and close the transaction
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_free_list_no_read_lock, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_free_list_read_lock, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_free_list_stale_reader, setup, teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_lmdb_size_test.c b/lib/ldb/tests/ldb_lmdb_size_test.c
new file mode 100644
index 0000000..95eba87
--- /dev/null
+++ b/lib/ldb/tests/ldb_lmdb_size_test.c
@@ -0,0 +1,249 @@
+/*
+ * lmdb backend specific tests for ldb
+ * Tests for truncated index keys
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * These tests confirm that database sizes of > 4GB are supported
+ * Due to the disk space requirement they are not run as part of the normal
+ * self test runs.
+ *
+ * Setup and tear down code copied from ldb_mod_op_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include <lmdb.h>
+
+
+#define TEST_BE "mdb"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+ /*
+ * We need to to set GUID index mode as it's required now required
+ * by LDB
+ */
+ struct ldb_ldif *ldif;
+ const char *index_ldif =
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+ /*
+ * Set the lmdb map size to 8Gb
+ */
+ const char *options[] = {"lmdb_env_size:8589934592", NULL};
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, options);
+ 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;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static void test_db_size_gt_4GB(void **state)
+{
+ int ret, x;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ const int MB = 1024 * 1024;
+ char *blob = NULL;
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+
+ blob = talloc_zero_size(tmp_ctx, (MB + 1));
+ assert_non_null(blob);
+ memset(blob, 'x', MB);
+
+
+ /*
+ * Write 6144 1Mb records to the database, this will require more than
+ * 4GiB of disk space
+ */
+ for (x = 0; x < 6144; x++) {
+ char uuid[24];
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * Generate a unique dn for each record
+ */
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test%d", x);
+ assert_non_null(msg->dn);
+
+ /*
+ * Generate a unique uuid for each added record
+ */
+ sprintf(uuid, "000000000000%04d", x);
+ ret = ldb_msg_add_string(msg, "objectUUID", uuid);
+ assert_int_equal(ret, 0);
+
+ ldb_transaction_start(test_ctx->ldb);
+ ret = ldb_msg_add_string(msg, "blob", blob);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, 0);
+ ldb_transaction_commit(test_ctx->ldb);
+
+ TALLOC_FREE(msg);
+ }
+ talloc_free(tmp_ctx);
+ {
+ struct stat s;
+ ret = stat(test_ctx->dbfile, &s);
+ assert_int_equal(ret, 0);
+ /*
+ * There should have been at least 6GiB written to disk
+ */
+ assert_true(s.st_size > (6144LL * MB));
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_db_size_gt_4GB,
+ ldbtest_setup,
+ ldbtest_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_lmdb_test.c b/lib/ldb/tests/ldb_lmdb_test.c
new file mode 100644
index 0000000..798a191
--- /dev/null
+++ b/lib/ldb/tests/ldb_lmdb_test.c
@@ -0,0 +1,590 @@
+/*
+ * lmdb backend specific tests for ldb
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * lmdb backend specific tests for ldb
+ *
+ * Setup and tear down code copied from ldb_mod_op_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "../ldb_tdb/ldb_tdb.h"
+#include "../ldb_mdb/ldb_mdb.h"
+#include "../ldb_key_value/ldb_kv.h"
+
+#define TEST_BE "mdb"
+
+#define LMDB_MAX_KEY_SIZE 511
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ 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;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static void test_ldb_add_key_len_gt_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ 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);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */
+ xs_size += 1; /* want key on char too long */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ 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", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_key_len_2x_gt_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ 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);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = 2 * LMDB_MAX_KEY_SIZE;
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ 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", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_key_len_eq_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ 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);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ 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", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static int ldbtest_setup_noguid(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static void test_ldb_add_special_key_len_gt_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ 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);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */
+ xs_size += 1; /* want key on char too long */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_special_key_len_eq_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ 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);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_dn_no_guid_mode(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ 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);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */
+ xs_size += 1; /* want key on char too long */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ 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", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM);
+
+ talloc_free(tmp_ctx);
+}
+
+static struct MDB_env *get_mdb_env(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ struct lmdb_private *lmdb = NULL;
+ struct MDB_env *env = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ldb_kv = talloc_get_type(data, struct ldb_kv_private);
+ assert_non_null(ldb_kv);
+
+ lmdb = ldb_kv->lmdb_private;
+ assert_non_null(lmdb);
+
+ env = lmdb->env;
+ assert_non_null(env);
+
+ return env;
+}
+
+static void test_multiple_opens(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ struct ldb_context *ldb3 = NULL;
+ struct MDB_env *env1 = NULL;
+ struct MDB_env *env2 = NULL;
+ struct MDB_env *env3 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+
+ 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, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+ /*
+ * We now have 3 ldb's open pointing to the same on disk database
+ * they should all share the same MDB_env
+ */
+ env1 = get_mdb_env(ldb1);
+ env2 = get_mdb_env(ldb2);
+ env3 = get_mdb_env(ldb3);
+
+ assert_ptr_equal(env1, env2);
+ assert_ptr_equal(env1, env3);
+}
+
+static void test_multiple_opens_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ struct MDB_env *env1 = NULL;
+ struct MDB_env *env2 = 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, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ env1 = get_mdb_env(ldb1);
+ env2 = get_mdb_env(ldb2);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_context *ldb3 = NULL;
+ struct MDB_env *env3 = 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);
+ }
+ env3 = get_mdb_env(ldb3);
+ if (env1 != env2) {
+ print_error(__location__": env1 != env2\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (env1 == env3) {
+ print_error(__location__": env1 == env3\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ 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_ldb_add_key_len_eq_max,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_key_len_gt_max,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_key_len_2x_gt_max,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_special_key_len_eq_max,
+ ldbtest_setup_noguid,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_special_key_len_gt_max,
+ ldbtest_setup_noguid,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_dn_no_guid_mode,
+ ldbtest_setup_noguid,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens,
+ 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);
+}
diff --git a/lib/ldb/tests/ldb_match_test.c b/lib/ldb/tests/ldb_match_test.c
new file mode 100644
index 0000000..1bb56d0
--- /dev/null
+++ b/lib/ldb/tests/ldb_match_test.c
@@ -0,0 +1,313 @@
+/*
+ * Tests exercising the ldb match operations.
+ *
+ *
+ * Copyright (C) Catalyst.NET Ltd 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "../common/ldb_match.c"
+
+#include "../include/ldb.h"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+};
+
+static int ldb_test_canonicalise(
+ struct ldb_context *ldb,
+ void *mem_ctx,
+ const struct ldb_val *in,
+ struct ldb_val *out)
+{
+ out->length = in->length;
+ out->data = in->data;
+ return 0;
+}
+
+static int setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ struct ldb_schema_syntax *syntax = NULL;
+ int ret;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ syntax = talloc_zero(test_ctx, struct ldb_schema_syntax);
+ assert_non_null(syntax);
+ syntax->canonicalise_fn = ldb_test_canonicalise;
+
+ ret = ldb_schema_attribute_add_with_syntax(
+ test_ctx->ldb, "a", LDB_ATTR_FLAG_FIXED, syntax);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ talloc_free(*state);
+ return 0;
+}
+
+static void escape_string(uint8_t *buf, size_t buflen,
+ const uint8_t *s, size_t len)
+{
+ size_t i;
+ size_t j = 0;
+ for (i = 0; i < len; i++) {
+ if (j == buflen - 1) {
+ goto fin;
+ }
+ if (s[i] >= 0x20) {
+ buf[j] = s[i];
+ j++;
+ } else {
+ if (j >= buflen - 4) {
+ goto fin;
+ }
+ /* utf-8 control char representation */
+ buf[j] = 0xE2;
+ buf[j + 1] = 0x90;
+ buf[j + 2] = 0x80 + s[i];
+ j+= 3;
+ }
+ }
+fin:
+ buf[j] = 0;
+}
+
+
+/*
+ * The wild card pattern "attribute=*" is parsed as an LDB_OP_PRESENT operation
+ * rather than a LDB_OP_????
+ *
+ * This test serves to document that behaviour, and to confirm that
+ * ldb_wildcard_compare handles this case appropriately.
+ */
+static void test_wildcard_match_star(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ bool matched = false;
+ int ret;
+
+ uint8_t value[] = "The value.......end";
+ struct ldb_val val = {
+ .data = value,
+ .length = (sizeof(value))
+ };
+ struct ldb_parse_tree *tree = ldb_parse_tree(ctx, "a=*");
+ assert_non_null(tree);
+
+ ret = ldb_wildcard_compare(ctx->ldb, tree, val, &matched);
+ assert_false(matched);
+ assert_int_equal(LDB_ERR_INAPPROPRIATE_MATCHING, ret);
+}
+
+/*
+ * Test basic wild card matching
+ *
+ */
+struct wildcard_test {
+ uint8_t *val;
+ size_t val_size;
+ const char *search;
+ bool should_match;
+ bool fold;
+};
+
+/*
+ * Q: Why this macro rather than plain struct values?
+ * A: So we can get the size of the const char[] value while it is still a
+ * true array, not a pointer.
+ *
+ * Q: but why not just use strlen?
+ * A: so values can contain '\0', which we supposedly allow.
+ */
+
+#define TEST_ENTRY(val, search, should_match, fold) \
+ { \
+ (uint8_t*)discard_const(val), \
+ sizeof(val) - 1, \
+ search, \
+ should_match, \
+ fold \
+ }
+
+static void test_wildcard_match(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ size_t failed = 0;
+ size_t i;
+ struct wildcard_test tests[] = {
+ TEST_ENTRY(" 1 0", "1*0*", true, true),
+ TEST_ENTRY(" 1 0", "1 *0", true, true),
+ TEST_ENTRY(" 1 0", "*1 0", true, true),
+ TEST_ENTRY("1 0", "*1 0", true, true),
+ TEST_ENTRY("The value.......end", "*end", true, true),
+ TEST_ENTRY("The value.......end", "*fend", false, true),
+ TEST_ENTRY("The value.......end", "*eel", false, true),
+ TEST_ENTRY("The value.......end", "*d", true, true),
+ TEST_ENTRY("The value.......end", "*D*", true, true),
+ TEST_ENTRY("The value.......end", "*e*d*", true, true),
+ TEST_ENTRY("end", "*e*d*", true, true),
+ TEST_ENTRY("end", " *e*d*", true, true),
+ TEST_ENTRY("1.0.0.0.0.0.0.0aaaaaaaaaaaa", "*aaaaa", true, true),
+ TEST_ENTRY("1.0..0.0.0.0.0.0.0aAaaaAAAAAAA", "*a", true, true),
+ TEST_ENTRY("1.0.0.0.0.0.0.0.0.0.0aaaa", "*aaaaa", false, true),
+ TEST_ENTRY("1.0.0.0.0.0.0.0.0.0.0", "*0.0", true, true),
+ TEST_ENTRY("1.0.0.0.0.0.0.0.0.0.0", "*0.0.0", true, true),
+ TEST_ENTRY("1.0.0.0.0.0.0.0.0.0", "1*0*0*0*0*0*0*0*0*0", true,
+ true),
+ TEST_ENTRY("1.0.0.0.0.0.0.0.0", "1*0*0*0*0*0*0*0*0*0", false,
+ true),
+ TEST_ENTRY("1.0.0.0.000.0.0.0.0", "1*0*0*0*0*0*0*0*0*0", true,
+ true),
+ TEST_ENTRY("1\n0\r0\t000.0.0.0.0", "1*0*0*0*0*0*0*0*0", true,
+ true),
+ /*
+ * We allow NUL bytes and redundant spaces in non-casefolding
+ * syntaxes.
+ */
+ TEST_ENTRY(" 1 0", "*1 0", true, false),
+ TEST_ENTRY(" 1 0", "*1 0", true, false),
+ TEST_ENTRY("1 0", "*1 0", false, false),
+ TEST_ENTRY("1\x00 x", "1*x", true, false),
+ TEST_ENTRY("1\x00 x", "*x", true, false),
+ TEST_ENTRY("1\x00 x", "*x*", true, false),
+ TEST_ENTRY("1\x00 x", "* *", true, false),
+ TEST_ENTRY("1\x00 x", "1*", true, false),
+ TEST_ENTRY("1\x00 b* x", "1*b*", true, false),
+ TEST_ENTRY("1.0..0.0.0.0.0.0.0aAaaaAAAAAAA", "*a", false, false),
+ };
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ bool matched;
+ int ret;
+ struct ldb_val val = {
+ .data = (uint8_t *)tests[i].val,
+ .length = tests[i].val_size
+ };
+ const char *attr = tests[i].fold ? "objectclass" : "birthLocation";
+ const char *s = talloc_asprintf(ctx, "%s=%s",
+ attr, tests[i].search);
+ struct ldb_parse_tree *tree = ldb_parse_tree(ctx, s);
+ assert_non_null(tree);
+ ret = ldb_wildcard_compare(ctx->ldb, tree, val, &matched);
+ if (ret != LDB_SUCCESS) {
+ uint8_t buf[100];
+ escape_string(buf, sizeof(buf),
+ tests[i].val, tests[i].val_size);
+ print_error("%zu val: «%s», search «%s» FAILED with %d\n",
+ i, buf, tests[i].search, ret);
+ failed++;
+ }
+ if (matched != tests[i].should_match) {
+ uint8_t buf[100];
+ escape_string(buf, sizeof(buf),
+ tests[i].val, tests[i].val_size);
+ print_error("%zu val: «%s», search «%s» should %s\n",
+ i, buf, tests[i].search,
+ matched ? "not match" : "match");
+ failed++;
+ }
+ }
+ if (failed != 0) {
+ fail_msg("wrong results for %zu/%zu wildcard searches\n",
+ failed, ARRAY_SIZE(tests));
+ }
+}
+
+#undef TEST_ENTRY
+
+
+/*
+ * ldb_handler_copy and ldb_val_dup over allocate by one and add a trailing '\0'
+ * to the data, to make them safe to use the C string functions on.
+ *
+ * However testing for the trailing '\0' is not the correct way to test for
+ * the end of a value, the length should be checked instead.
+ */
+static void test_wildcard_match_end_condition(void **state)
+{
+ struct ldbtest_ctx *ctx = *state;
+ bool matched = false;
+
+ uint8_t value[] = "hellomynameisbobx";
+ struct ldb_val val = {
+ .data = talloc_memdup(NULL, value, sizeof(value)),
+ .length = (sizeof(value) - 2)
+ };
+ struct ldb_parse_tree *tree = ldb_parse_tree(ctx, "a=*hello*mynameis*bob");
+ assert_non_null(tree);
+
+ ldb_wildcard_compare(ctx->ldb, tree, val, &matched);
+ assert_true(matched);
+}
+
+/*
+ * Note: to run under valgrind use:
+ * valgrind \
+ * --suppressions=lib/ldb/tests/ldb_match_test.valgrind \
+ * bin/ldb_match_test
+ */
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_wildcard_match_star,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_wildcard_match,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_wildcard_match_end_condition,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_match_test.valgrind b/lib/ldb/tests/ldb_match_test.valgrind
new file mode 100644
index 0000000..660bd5a
--- /dev/null
+++ b/lib/ldb/tests/ldb_match_test.valgrind
@@ -0,0 +1,16 @@
+{
+ Memory allocated in set-up
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:setup
+}
+{
+ Memory allocated by ldb_init
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:ldb_init
+}
diff --git a/lib/ldb/tests/ldb_mod_op_test.c b/lib/ldb/tests/ldb_mod_op_test.c
new file mode 100644
index 0000000..f620fc1
--- /dev/null
+++ b/lib/ldb/tests/ldb_mod_op_test.c
@@ -0,0 +1,4724 @@
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+
+#define TEVENT_DEPRECATED 1
+#include <tevent.h>
+
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+
+#define DEFAULT_BE "tdb"
+
+#ifndef TEST_BE
+#define TEST_BE DEFAULT_BE
+#endif /* TEST_BE */
+
+#ifdef TEST_LMDB
+#include "lmdb.h"
+#include "../ldb_tdb/ldb_tdb.h"
+#include "../ldb_mdb/ldb_mdb.h"
+#endif
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+ char *debug_string;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static void test_connect(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ int ret;
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ 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);
+
+ 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;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static void test_ldb_add(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_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);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_search(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;
+ struct ldb_dn *basedn;
+ struct ldb_dn *basedn2;
+ struct ldb_result *result = NULL;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test");
+ assert_non_null(basedn);
+
+ ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 0);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ msg->dn = basedn;
+ assert_non_null(msg->dn);
+
+ 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);
+
+ basedn2 = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test2");
+ assert_non_null(basedn2);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ msg->dn = basedn2;
+ assert_non_null(msg->dn);
+
+ 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);
+
+ ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ assert_string_equal(ldb_dn_get_linearized(result->msgs[0]->dn),
+ ldb_dn_get_linearized(basedn));
+
+ ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn2,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ assert_string_equal(ldb_dn_get_linearized(result->msgs[0]->dn),
+ ldb_dn_get_linearized(basedn2));
+
+ talloc_free(tmp_ctx);
+}
+
+static int base_search_count(struct ldbtest_ctx *test_ctx, const char *entry_dn)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+ int ret;
+ int count;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "%s", entry_dn);
+ assert_non_null(basedn);
+
+ ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+
+ count = result->count;
+ talloc_free(tmp_ctx);
+ return count;
+}
+
+static int sub_search_count(struct ldbtest_ctx *test_ctx,
+ const char *base_dn,
+ const char *filter)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+ int ret;
+ int count;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "%s", base_dn);
+ assert_non_null(basedn);
+
+ ret = ldb_search(test_ctx->ldb, tmp_ctx, &result, basedn,
+ LDB_SCOPE_SUBTREE, NULL, "%s", filter);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+
+ count = result->count;
+ talloc_free(tmp_ctx);
+ return count;
+}
+
+/* In general it would be better if utility test functions didn't assert
+ * but only returned a value, then assert in the test shows correct
+ * line
+ */
+static void assert_dn_exists(struct ldbtest_ctx *test_ctx,
+ const char *entry_dn)
+{
+ int count;
+
+ count = base_search_count(test_ctx, entry_dn);
+ assert_int_equal(count, 1);
+}
+
+static void assert_dn_doesnt_exist(struct ldbtest_ctx *test_ctx,
+ const char *entry_dn)
+{
+ int count;
+
+ count = base_search_count(test_ctx, entry_dn);
+ assert_int_equal(count, 0);
+}
+
+static void add_dn_with_cn(struct ldbtest_ctx *test_ctx,
+ struct ldb_dn *dn,
+ const char *cn_value,
+ const char *uuid_value)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message *msg;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ assert_dn_doesnt_exist(test_ctx,
+ ldb_dn_get_linearized(dn));
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+ msg->dn = dn;
+
+ 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);
+
+ assert_dn_exists(test_ctx,
+ ldb_dn_get_linearized(dn));
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_del(void **state)
+{
+ int ret;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ const char *basedn = "dc=ldb_del_test";
+ struct ldb_dn *dn;
+
+ 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",
+ "0123456789abcdef");
+
+ ret = ldb_delete(test_ctx->ldb, dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_dn_doesnt_exist(test_ctx, basedn);
+}
+
+static void test_ldb_del_noexist(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ struct ldb_dn *basedn;
+ int ret;
+
+ basedn = ldb_dn_new(test_ctx, test_ctx->ldb, "dc=nosuchplace");
+ assert_non_null(basedn);
+
+ ret = ldb_delete(test_ctx->ldb, basedn);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+}
+
+static void test_ldb_handle(void **state)
+{
+ int ret;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *basedn;
+ struct ldb_request *request = NULL;
+ struct ldb_request *request2 = NULL;
+ struct ldb_result *res = NULL;
+ const char *attrs[] = { "cn", NULL };
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test");
+ assert_non_null(basedn);
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ assert_non_null(res);
+
+ ret = ldb_build_search_req(&request, test_ctx->ldb, tmp_ctx,
+ basedn, LDB_SCOPE_BASE,
+ NULL, attrs, NULL, res,
+ ldb_search_default_callback,
+ NULL);
+ assert_int_equal(ret, 0);
+
+ /* We are against ldb_tdb, so expect private event contexts */
+ assert_ptr_not_equal(ldb_handle_get_event_context(request->handle),
+ ldb_get_event_context(test_ctx->ldb));
+
+ ret = ldb_build_search_req(&request2, test_ctx->ldb, tmp_ctx,
+ basedn, LDB_SCOPE_BASE,
+ NULL, attrs, NULL, res,
+ ldb_search_default_callback,
+ request);
+ assert_int_equal(ret, 0);
+
+ /* Expect that same event context will be chained */
+ assert_ptr_equal(ldb_handle_get_event_context(request->handle),
+ ldb_handle_get_event_context(request2->handle));
+
+ /* Now force this to use the global context */
+ ldb_handle_use_global_event_context(request2->handle);
+ assert_ptr_equal(ldb_handle_get_event_context(request2->handle),
+ ldb_get_event_context(test_ctx->ldb));
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_build_search_req(void **state)
+{
+ int ret;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *basedn;
+ struct ldb_request *request = NULL;
+ struct ldb_request *request2 = NULL;
+ struct ldb_result *res = NULL;
+ const char *attrs[] = { "cn", NULL };
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, test_ctx->ldb, "dc=test");
+ assert_non_null(basedn);
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ assert_non_null(res);
+
+ ret = ldb_build_search_req(&request, test_ctx->ldb, tmp_ctx,
+ basedn, LDB_SCOPE_BASE,
+ NULL, attrs, NULL, res,
+ ldb_search_default_callback,
+ NULL);
+ assert_int_equal(ret, 0);
+
+ assert_int_equal(request->operation, LDB_SEARCH);
+ assert_ptr_equal(request->op.search.base, basedn);
+ assert_int_equal(request->op.search.scope, LDB_SCOPE_BASE);
+ assert_non_null(request->op.search.tree);
+ assert_ptr_equal(request->op.search.attrs, attrs);
+ assert_ptr_equal(request->context, res);
+ assert_ptr_equal(request->callback, ldb_search_default_callback);
+
+ ret = ldb_build_search_req(&request2, test_ctx->ldb, tmp_ctx,
+ basedn, LDB_SCOPE_BASE,
+ NULL, attrs, NULL, res,
+ ldb_search_default_callback,
+ request);
+ assert_int_equal(ret, 0);
+ assert_ptr_equal(request, request2->handle->parent);
+ assert_int_equal(request->starttime, request2->starttime);
+ assert_int_equal(request->timeout, request2->timeout);
+
+ talloc_free(tmp_ctx);
+}
+
+static void add_keyval(struct ldbtest_ctx *test_ctx,
+ const char *key,
+ const char *val,
+ const char *uuid)
+{
+ int ret;
+ struct ldb_message *msg;
+
+ msg = ldb_msg_new(test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "%s=%s", key, val);
+ assert_non_null(msg->dn);
+
+ 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);
+
+ talloc_free(msg);
+}
+
+static struct ldb_result *get_keyval(struct ldbtest_ctx *test_ctx,
+ const char *key,
+ const char *val)
+{
+ int ret;
+ struct ldb_result *result;
+ struct ldb_dn *basedn;
+
+ basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "%s=%s", key, val);
+ assert_non_null(basedn);
+
+ ret = ldb_search(test_ctx->ldb, test_ctx, &result, basedn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, 0);
+
+ return result;
+}
+
+static void test_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");
+
+ /* commit lev-0 transaction */
+ ret = ldb_transaction_commit(test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ /* 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);
+
+ res = get_keyval(test_ctx, "vegetable", "carrot");
+ assert_non_null(res);
+ assert_int_equal(res->count, 1);
+
+ res = get_keyval(test_ctx, "fruit", "apple");
+ assert_non_null(res);
+ 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;
+};
+
+struct keyval {
+ const char *key;
+ const char *val;
+};
+
+static struct ldb_message *build_mod_msg(TALLOC_CTX *mem_ctx,
+ struct ldbtest_ctx *test_ctx,
+ const char *dn,
+ int modify_flags,
+ struct keyval *kvs)
+{
+ struct ldb_message *msg;
+ int ret;
+ int i;
+
+ msg = ldb_msg_new(mem_ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "%s", dn);
+ assert_non_null(msg->dn);
+
+ for (i = 0; kvs[i].key != NULL; i++) {
+ if (modify_flags) {
+ ret = ldb_msg_add_empty(msg, kvs[i].key,
+ modify_flags, NULL);
+ assert_int_equal(ret, 0);
+ }
+
+ if (kvs[i].val) {
+ ret = ldb_msg_add_string(msg, kvs[i].key, kvs[i].val);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+ }
+
+ return msg;
+}
+
+static void ldb_test_add_data(TALLOC_CTX *mem_ctx,
+ struct ldbtest_ctx *ldb_test_ctx,
+ const char *basedn,
+ struct keyval *kvs)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message *msg;
+ struct ldb_result *result = NULL;
+ int ret;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = build_mod_msg(tmp_ctx, ldb_test_ctx,
+ basedn, 0, kvs);
+ assert_non_null(msg);
+
+ ret = ldb_add(ldb_test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_search(ldb_test_ctx->ldb, tmp_ctx, &result, msg->dn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+ assert_string_equal(ldb_dn_get_linearized(result->msgs[0]->dn),
+ ldb_dn_get_linearized(msg->dn));
+
+ talloc_free(tmp_ctx);
+}
+
+static void ldb_test_remove_data(TALLOC_CTX *mem_ctx,
+ struct ldbtest_ctx *ldb_test_ctx,
+ const char *strdn)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *basedn;
+ int ret;
+ size_t count;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ assert_non_null(tmp_ctx);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, ldb_test_ctx->ldb,
+ "%s", strdn);
+ assert_non_null(basedn);
+
+ ret = ldb_delete(ldb_test_ctx->ldb, basedn);
+ assert_true(ret == LDB_SUCCESS || ret == LDB_ERR_NO_SUCH_OBJECT);
+
+ count = base_search_count(ldb_test_ctx, ldb_dn_get_linearized(basedn));
+ assert_int_equal(count, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static void mod_test_add_data(struct ldb_mod_test_ctx *mod_test_ctx,
+ struct keyval *kvs)
+{
+ ldb_test_add_data(mod_test_ctx,
+ mod_test_ctx->ldb_test_ctx,
+ mod_test_ctx->entry_dn,
+ kvs);
+}
+
+static void mod_test_remove_data(struct ldb_mod_test_ctx *mod_test_ctx)
+{
+ ldb_test_remove_data(mod_test_ctx,
+ mod_test_ctx->ldb_test_ctx,
+ mod_test_ctx->entry_dn);
+}
+
+static struct ldb_result *run_mod_test(struct ldb_mod_test_ctx *mod_test_ctx,
+ int modify_flags,
+ struct keyval *kvs)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_result *res;
+ struct ldb_message *mod_msg;
+ struct ldb_dn *basedn;
+ struct ldbtest_ctx *ldb_test_ctx;
+ int ret;
+
+ ldb_test_ctx = mod_test_ctx->ldb_test_ctx;
+
+ tmp_ctx = talloc_new(mod_test_ctx);
+ assert_non_null(tmp_ctx);
+
+ mod_msg = build_mod_msg(tmp_ctx, ldb_test_ctx, mod_test_ctx->entry_dn,
+ modify_flags, kvs);
+ assert_non_null(mod_msg);
+
+ ret = ldb_modify(ldb_test_ctx->ldb, mod_msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ basedn = ldb_dn_new_fmt(tmp_ctx, ldb_test_ctx->ldb,
+ "%s", mod_test_ctx->entry_dn);
+ assert_non_null(basedn);
+
+ ret = ldb_search(ldb_test_ctx->ldb, mod_test_ctx, &res, basedn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(res);
+ assert_int_equal(res->count, 1);
+ assert_string_equal(ldb_dn_get_linearized(res->msgs[0]->dn),
+ ldb_dn_get_linearized(mod_msg->dn));
+
+ talloc_free(tmp_ctx);
+ return res;
+}
+
+static int ldb_modify_test_setup(void **state)
+{
+ struct ldbtest_ctx *ldb_test_ctx;
+ struct ldb_mod_test_ctx *mod_test_ctx;
+ struct keyval kvs[] = {
+ { "cn", "test_mod_cn" },
+ { "objectUUID", "0123456789abcdef"},
+ { NULL, NULL },
+ };
+
+ ldbtest_setup((void **) &ldb_test_ctx);
+
+ mod_test_ctx = talloc(ldb_test_ctx, struct ldb_mod_test_ctx);
+ assert_non_null(mod_test_ctx);
+
+ mod_test_ctx->entry_dn = "dc=mod_test_entry";
+ mod_test_ctx->ldb_test_ctx = ldb_test_ctx;
+
+ mod_test_remove_data(mod_test_ctx);
+ mod_test_add_data(mod_test_ctx, kvs);
+ *state = mod_test_ctx;
+ return 0;
+}
+
+static int ldb_modify_test_teardown(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct ldbtest_ctx *ldb_test_ctx;
+
+ ldb_test_ctx = mod_test_ctx->ldb_test_ctx;
+
+ mod_test_remove_data(mod_test_ctx);
+ talloc_free(mod_test_ctx);
+
+ ldbtest_teardown((void **) &ldb_test_ctx);
+ return 0;
+}
+
+static void test_ldb_modify_add_key(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct keyval mod_kvs[] = {
+ { "name", "test_mod_name" },
+ { NULL, NULL },
+ };
+ struct ldb_result *res;
+ struct ldb_message_element *el;
+
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_ADD, mod_kvs);
+ assert_non_null(res);
+
+ /* Check cn is intact and name was added */
+ assert_int_equal(res->count, 1);
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_non_null(el);
+ assert_int_equal(el->num_values, 1);
+ assert_string_equal((const char *)el->values[0].data, "test_mod_cn");
+
+ el = ldb_msg_find_element(res->msgs[0], "name");
+ assert_non_null(el);
+ assert_int_equal(el->num_values, 1);
+ assert_string_equal((const char *)el->values[0].data, "test_mod_name");
+}
+
+static void test_ldb_modify_extend_key(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct keyval mod_kvs[] = {
+ { "cn", "test_mod_cn2" },
+ { NULL, NULL },
+ };
+ struct ldb_result *res;
+ struct ldb_message_element *el;
+
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_ADD, mod_kvs);
+ assert_non_null(res);
+
+ /* Check cn was extended with another value */
+ assert_int_equal(res->count, 1);
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_non_null(el);
+ assert_int_equal(el->num_values, 2);
+ assert_string_equal((const char *)el->values[0].data, "test_mod_cn");
+ assert_string_equal((const char *)el->values[1].data, "test_mod_cn2");
+}
+
+static void test_ldb_modify_add_key_noval(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct ldb_message *mod_msg;
+ struct ldbtest_ctx *ldb_test_ctx;
+ struct ldb_message_element *el;
+ int ret;
+
+ ldb_test_ctx = mod_test_ctx->ldb_test_ctx;
+
+ mod_msg = ldb_msg_new(mod_test_ctx);
+ assert_non_null(mod_msg);
+
+ mod_msg->dn = ldb_dn_new_fmt(mod_msg, ldb_test_ctx->ldb,
+ "%s", mod_test_ctx->entry_dn);
+ assert_non_null(mod_msg->dn);
+
+ el = talloc_zero(mod_msg, struct ldb_message_element);
+ el->flags = LDB_FLAG_MOD_ADD;
+ assert_non_null(el);
+ el->name = talloc_strdup(el, "cn");
+ assert_non_null(el->name);
+
+ mod_msg->elements = el;
+ mod_msg->num_elements = 1;
+
+ ret = ldb_modify(ldb_test_ctx->ldb, mod_msg);
+ assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
+}
+
+static void test_ldb_modify_replace_key(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ const char *new_cn = "new_cn";
+ struct keyval mod_kvs[] = {
+ { "cn", new_cn },
+ { NULL, NULL },
+ };
+ struct ldb_result *res;
+ struct ldb_message_element *el;
+
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, mod_kvs);
+ assert_non_null(res);
+
+ /* Check cn was replaced */
+ assert_int_equal(res->count, 1);
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_non_null(el);
+ assert_int_equal(el->num_values, 1);
+ assert_string_equal((const char *)el->values[0].data, new_cn);
+}
+
+static void test_ldb_modify_replace_noexist_key(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct keyval mod_kvs[] = {
+ { "name", "name_val" },
+ { NULL, NULL },
+ };
+ struct ldb_result *res;
+ struct ldb_message_element *el;
+
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, mod_kvs);
+ assert_non_null(res);
+
+ /* Check cn is intact and name was added */
+ assert_int_equal(res->count, 1);
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_non_null(el);
+ assert_int_equal(el->num_values, 1);
+ assert_string_equal((const char *)el->values[0].data, "test_mod_cn");
+
+ el = ldb_msg_find_element(res->msgs[0], mod_kvs[0].key);
+ assert_non_null(el);
+ assert_int_equal(el->num_values, 1);
+ assert_string_equal((const char *)el->values[0].data, mod_kvs[0].val);
+}
+
+static void test_ldb_modify_replace_zero_vals(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct ldb_message_element *el;
+ struct ldb_result *res;
+ struct keyval kvs[] = {
+ { "cn", NULL },
+ { NULL, NULL },
+ };
+
+ /* cn must be gone */
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, kvs);
+ assert_non_null(res);
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_null(el);
+}
+
+static void test_ldb_modify_replace_noexist_key_zero_vals(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct ldb_message_element *el;
+ struct ldb_result *res;
+ struct keyval kvs[] = {
+ { "noexist_key", NULL },
+ { NULL, NULL },
+ };
+
+ /* cn must be gone */
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_REPLACE, kvs);
+ assert_non_null(res);
+
+ /* cn should be intact */
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_non_null(el);
+}
+
+static void test_ldb_modify_del_key(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct ldb_message_element *el;
+ struct ldb_result *res;
+ struct keyval kvs[] = {
+ { "cn", NULL },
+ { NULL, NULL },
+ };
+
+ /* cn must be gone */
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_DELETE, kvs);
+ assert_non_null(res);
+
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_null(el);
+}
+
+static void test_ldb_modify_del_keyval(void **state)
+{
+ struct ldb_mod_test_ctx *mod_test_ctx = \
+ talloc_get_type_abort(*state,
+ struct ldb_mod_test_ctx);
+ struct ldb_message_element *el;
+ struct ldb_result *res;
+ struct keyval kvs[] = {
+ { "cn", "test_mod_cn" },
+ { NULL, NULL },
+ };
+
+ /* cn must be gone */
+ res = run_mod_test(mod_test_ctx, LDB_FLAG_MOD_DELETE, kvs);
+ assert_non_null(res);
+
+ el = ldb_msg_find_element(res->msgs[0], "cn");
+ assert_null(el);
+}
+
+struct search_test_ctx {
+ struct ldbtest_ctx *ldb_test_ctx;
+ const char *base_dn;
+};
+
+static char *get_full_dn(TALLOC_CTX *mem_ctx,
+ struct search_test_ctx *search_test_ctx,
+ const char *rdn)
+{
+ char *full_dn;
+
+ full_dn = talloc_asprintf(mem_ctx,
+ "%s,%s", rdn, search_test_ctx->base_dn);
+ assert_non_null(full_dn);
+
+ return full_dn;
+}
+
+static void search_test_add_data(struct search_test_ctx *search_test_ctx,
+ const char *rdn,
+ struct keyval *kvs)
+{
+ char *full_dn;
+
+ full_dn = get_full_dn(search_test_ctx, search_test_ctx, rdn);
+
+ ldb_test_add_data(search_test_ctx,
+ search_test_ctx->ldb_test_ctx,
+ full_dn,
+ kvs);
+}
+
+static void search_test_remove_data(struct search_test_ctx *search_test_ctx,
+ const char *rdn)
+{
+ char *full_dn;
+
+ full_dn = talloc_asprintf(search_test_ctx,
+ "%s,%s", rdn, search_test_ctx->base_dn);
+ assert_non_null(full_dn);
+
+ ldb_test_remove_data(search_test_ctx,
+ search_test_ctx->ldb_test_ctx,
+ full_dn);
+}
+
+static int ldb_search_test_setup(void **state)
+{
+ struct ldbtest_ctx *ldb_test_ctx;
+ struct search_test_ctx *search_test_ctx;
+ struct keyval kvs[] = {
+ { "cn", "test_search_cn" },
+ { "cn", "test_search_cn2" },
+ { "uid", "test_search_uid" },
+ { "uid", "test_search_uid2" },
+ { "objectUUID", "0123456789abcde0"},
+ { NULL, NULL },
+ };
+ struct keyval kvs2[] = {
+ { "cn", "test_search_2_cn" },
+ { "cn", "test_search_2_cn2" },
+ { "uid", "test_search_2_uid" },
+ { "uid", "test_search_2_uid2" },
+ { "objectUUID", "0123456789abcde1"},
+ { NULL, NULL },
+ };
+
+ ldbtest_setup((void **) &ldb_test_ctx);
+
+ search_test_ctx = talloc(ldb_test_ctx, struct search_test_ctx);
+ assert_non_null(search_test_ctx);
+
+ search_test_ctx->base_dn = "dc=search_test_entry";
+ search_test_ctx->ldb_test_ctx = ldb_test_ctx;
+
+ search_test_remove_data(search_test_ctx, "cn=test_search_cn");
+ search_test_add_data(search_test_ctx, "cn=test_search_cn", kvs);
+
+ search_test_remove_data(search_test_ctx, "cn=test_search_2_cn");
+ search_test_add_data(search_test_ctx, "cn=test_search_2_cn", kvs2);
+
+ *state = search_test_ctx;
+ return 0;
+}
+
+static int ldb_search_test_teardown(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ struct ldbtest_ctx *ldb_test_ctx;
+
+ ldb_test_ctx = search_test_ctx->ldb_test_ctx;
+
+ search_test_remove_data(search_test_ctx, "cn=test_search_cn");
+ search_test_remove_data(search_test_ctx, "cn=test_search_2_cn");
+ ldbtest_teardown((void **) &ldb_test_ctx);
+ return 0;
+}
+
+static void assert_attr_has_vals(struct ldb_message *msg,
+ const char *attr,
+ const char *vals[],
+ const size_t nvals)
+{
+ struct ldb_message_element *el;
+ size_t i;
+
+ el = ldb_msg_find_element(msg, attr);
+ assert_non_null(el);
+
+ assert_int_equal(el->num_values, nvals);
+ for (i = 0; i < nvals; i++) {
+ assert_string_equal((const char *)el->values[i].data,
+ vals[i]);
+ }
+}
+
+static void assert_has_no_attr(struct ldb_message *msg,
+ const char *attr)
+{
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(msg, attr);
+ assert_null(el);
+}
+
+static bool has_dn(struct ldb_message *msg, const char *dn)
+{
+ const char *msgdn;
+
+ msgdn = ldb_dn_get_linearized(msg->dn);
+ if (strcmp(dn, msgdn) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+static void test_search_match_none(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ size_t count;
+
+ count = base_search_count(search_test_ctx->ldb_test_ctx,
+ "dc=no_such_entry");
+ assert_int_equal(count, 0);
+}
+
+static void test_search_match_one(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ int ret;
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+ const char *cn_vals[] = { "test_search_cn",
+ "test_search_cn2" };
+ const char *uid_vals[] = { "test_search_uid",
+ "test_search_uid2" };
+
+ 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_search(search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE, NULL,
+ "cn=test_search_cn");
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+
+ assert_attr_has_vals(result->msgs[0], "cn", cn_vals, 2);
+ assert_attr_has_vals(result->msgs[0], "uid", uid_vals, 2);
+}
+
+static void test_search_match_filter(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ int ret;
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+ const char *cn_vals[] = { "test_search_cn",
+ "test_search_cn2" };
+ const char *attrs[] = { "cn", NULL };
+
+ 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_search(search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE,
+ attrs,
+ "cn=test_search_cn");
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 1);
+
+ assert_attr_has_vals(result->msgs[0], "cn", cn_vals, 2);
+ assert_has_no_attr(result->msgs[0], "uid");
+}
+
+static void assert_expected(struct search_test_ctx *search_test_ctx,
+ struct ldb_message *msg)
+{
+ char *full_dn1;
+ char *full_dn2;
+ const char *cn_vals[] = { "test_search_cn",
+ "test_search_cn2" };
+ const char *uid_vals[] = { "test_search_uid",
+ "test_search_uid2" };
+ const char *cn2_vals[] = { "test_search_2_cn",
+ "test_search_2_cn2" };
+ const char *uid2_vals[] = { "test_search_2_uid",
+ "test_search_2_uid2" };
+
+ full_dn1 = get_full_dn(search_test_ctx,
+ search_test_ctx,
+ "cn=test_search_cn");
+
+ full_dn2 = get_full_dn(search_test_ctx,
+ search_test_ctx,
+ "cn=test_search_2_cn");
+
+ if (has_dn(msg, full_dn1) == true) {
+ assert_attr_has_vals(msg, "cn", cn_vals, 2);
+ assert_attr_has_vals(msg, "uid", uid_vals, 2);
+ } else if (has_dn(msg, full_dn2) == true) {
+ assert_attr_has_vals(msg, "cn", cn2_vals, 2);
+ assert_attr_has_vals(msg, "uid", uid2_vals, 2);
+ } else {
+ fail();
+ }
+}
+
+static void test_search_match_both(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ int ret;
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+
+ 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_search(search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE, NULL,
+ "cn=test_search_*");
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 2);
+
+ assert_expected(search_test_ctx, result->msgs[0]);
+ assert_expected(search_test_ctx, result->msgs[1]);
+}
+
+static void test_search_match_basedn(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ int ret;
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+ struct ldb_message *msg;
+
+ basedn = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "dc=nosuchdn");
+ assert_non_null(basedn);
+
+ ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE, NULL,
+ "cn=*");
+ assert_int_equal(ret, 0);
+
+ /* Add 'checkBaseOnSearch' to @OPTIONS */
+ msg = ldb_msg_new(search_test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(msg,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "@OPTIONS");
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "checkBaseOnSearch", "TRUE");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb, msg);
+ assert_int_equal(ret, 0);
+
+ /* Search again */
+ /* The search should return LDB_ERR_NO_SUCH_OBJECT */
+ ret = ldb_search(search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_SUBTREE, NULL,
+ "cn=*");
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+
+ ret = ldb_delete(search_test_ctx->ldb_test_ctx->ldb, msg->dn);
+ assert_int_equal(ret, 0);
+}
+
+
+/*
+ * This test is complex.
+ * The purpose is to test for a deadlock detected between ldb_search()
+ * and ldb_transaction_commit(). The deadlock happens if in process
+ * (1) and (2):
+ * - (1) the all-record lock is taken in ltdb_search()
+ * - (2) the ldb_transaction_start() call is made
+ * - (1) an un-indexed search starts (forced here by doing it in
+ * the callback
+ * - (2) the ldb_transaction_commit() is called.
+ * This returns LDB_ERR_BUSY if the deadlock is detected
+ *
+ * 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()
+ */
+
+struct search_against_transaction_ctx {
+ struct ldbtest_ctx *test_ctx;
+ int res_count;
+ pid_t child_pid;
+ struct ldb_dn *basedn;
+};
+
+static int test_ldb_search_against_transaction_callback2(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct search_against_transaction_ctx *ctx = req->context;
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ctx->res_count++;
+ if (ctx->res_count != 1) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ break;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ return 0;
+
+}
+
+/*
+ * This purpose of this callback is to trigger a transaction in
+ * the child process while the all-record lock is held, but before
+ * 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.31 ltdb_search() forgets to take the all-record
+ * lock (except the very first time) due to a ref-counting bug.
+ *
+ */
+
+static int test_ldb_search_against_transaction_callback1(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret, ret2;
+ int pipes[2];
+ char buf[2];
+ struct search_against_transaction_ctx *ctx = req->context;
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ ctx->child_pid = fork();
+ if (ctx->child_pid == 0) {
+ TALLOC_CTX *tmp_ctx = NULL;
+ 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);
+ }
+
+ ctx->test_ctx->ldb = ldb_init(ctx->test_ctx,
+ ctx->test_ctx->ev);
+ if (ctx->test_ctx->ldb == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_connect(ctx->test_ctx->ldb,
+ ctx->test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ exit(ret);
+ }
+
+ tmp_ctx = talloc_new(ctx->test_ctx);
+ if (tmp_ctx == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb,
+ "dc=test");
+ if (msg->dn == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ if (ret != 0) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_transaction_start(ctx->test_ctx->ldb);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ 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);
+ }
+
+ ret = ldb_transaction_commit(ctx->test_ctx->ldb);
+ exit(ret);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ /* This search must be unindexed (ie traverse in tdb) */
+ ret = ldb_build_search_req(&req,
+ ctx->test_ctx->ldb,
+ ctx->test_ctx,
+ ctx->basedn,
+ LDB_SCOPE_SUBTREE,
+ "cn=*", NULL,
+ NULL,
+ ctx,
+ test_ldb_search_against_transaction_callback2,
+ NULL);
+ /*
+ * we don't assert on these return codes until after the search is
+ * finished, or the clean up will fail because we hold locks.
+ */
+
+ ret2 = ldb_request(ctx->test_ctx->ldb, req);
+
+ if (ret2 == LDB_SUCCESS) {
+ ret2 = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ assert_int_equal(ret, 0);
+ assert_int_equal(ret2, 0);
+ assert_int_equal(ctx->res_count, 2);
+
+ return LDB_SUCCESS;
+}
+
+static void test_ldb_search_against_transaction(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ struct search_against_transaction_ctx
+ ctx =
+ { .res_count = 0,
+ .test_ctx = search_test_ctx->ldb_test_ctx
+ };
+
+ int ret;
+ struct ldb_request *req;
+ pid_t pid;
+ int wstatus;
+ struct ldb_dn *base_search_dn;
+
+ tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev);
+
+ base_search_dn
+ = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "cn=test_search_cn,%s",
+ search_test_ctx->base_dn);
+ assert_non_null(base_search_dn);
+
+ ctx.basedn
+ = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "%s",
+ search_test_ctx->base_dn);
+ assert_non_null(ctx.basedn);
+
+
+ /* This search must be indexed (ie no traverse in tdb) */
+ ret = ldb_build_search_req(&req,
+ search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ base_search_dn,
+ LDB_SCOPE_BASE,
+ "cn=*", NULL,
+ NULL,
+ &ctx,
+ test_ldb_search_against_transaction_callback1,
+ NULL);
+ assert_int_equal(ret, 0);
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ assert_int_equal(ret, 0);
+ assert_int_equal(ctx.res_count, 2);
+
+ pid = waitpid(ctx.child_pid, &wstatus, 0);
+ assert_int_equal(pid, ctx.child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+
+}
+
+/*
+ * This test is also complex.
+ * The purpose is to test if a modify can occur during an ldb_search()
+ * This would be a failure if if in process
+ * (1) and (2):
+ * - (1) ltdb_search() starts and calls back for one entry
+ * - (2) one of the entries to be matched is modified
+ * - (1) the indexed search tries to return the modified entry, but
+ * it is no longer found, either:
+ * - despite it still matching (dn changed)
+ * - it no longer matching (attrs changed)
+ *
+ * We also try un-indexed to show that the behaviour differs on this
+ * point, which it should not (an index should only impact search
+ * speed).
+ */
+
+struct modify_during_search_test_ctx {
+ struct ldbtest_ctx *test_ctx;
+ int res_count;
+ pid_t child_pid;
+ struct ldb_dn *basedn;
+ bool got_cn;
+ bool got_2_cn;
+ bool rename;
+};
+
+/*
+ * This purpose of this callback is to trigger a write in
+ * the child process while a search is in progress.
+ *
+ * In tdb 1.3.12 tdb_traverse_read() take the read transaction lock
+ * 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.
+ *
+ * We assume that if the write will proceed, it will proceed in a 3
+ * second window after the function is called.
+ */
+
+static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret;
+ int pipes[2];
+ char buf[2];
+ struct modify_during_search_test_ctx *ctx = req->context;
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ const struct ldb_val *cn_val
+ = ldb_dn_get_component_val(ares->message->dn, 0);
+ const char *cn = (char *)cn_val->data;
+ ctx->res_count++;
+ if (strcmp(cn, "test_search_cn") == 0) {
+ ctx->got_cn = true;
+ } else if (strcmp(cn, "test_search_2_cn") == 0) {
+ ctx->got_2_cn = true;
+ }
+ if (ctx->res_count == 2) {
+ return LDB_SUCCESS;
+ }
+ break;
+ }
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ ctx->child_pid = fork();
+ if (ctx->child_pid == 0 && ctx->rename) {
+ TALLOC_CTX *tmp_ctx = NULL;
+ 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);
+ }
+
+ ctx->test_ctx->ldb = ldb_init(ctx->test_ctx,
+ ctx->test_ctx->ev);
+ if (ctx->test_ctx->ldb == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_connect(ctx->test_ctx->ldb,
+ ctx->test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ exit(ret);
+ }
+
+ tmp_ctx = talloc_new(ctx->test_ctx);
+ if (tmp_ctx == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ctx->got_cn) {
+ /* Modify the other one */
+ dn = ldb_dn_new_fmt(tmp_ctx, ctx->test_ctx->ldb,
+ "cn=test_search_2_cn,"
+ "dc=search_test_entry");
+ } else {
+ dn = ldb_dn_new_fmt(tmp_ctx, ctx->test_ctx->ldb,
+ "cn=test_search_cn,"
+ "dc=search_test_entry");
+ }
+ if (dn == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ new_dn = ldb_dn_new_fmt(tmp_ctx, ctx->test_ctx->ldb,
+ "cn=test_search_cn_renamed,"
+ "dc=search_test_entry");
+ if (new_dn == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_transaction_start(ctx->test_ctx->ldb);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ if (write(pipes[1], "GO", 2) != 2) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_rename(ctx->test_ctx->ldb, dn, new_dn);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ ret = ldb_transaction_commit(ctx->test_ctx->ldb);
+ exit(ret);
+
+ } else if (ctx->child_pid == 0) {
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_message *msg;
+ 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);
+ }
+
+ ctx->test_ctx->ldb = ldb_init(ctx->test_ctx,
+ ctx->test_ctx->ev);
+ if (ctx->test_ctx->ldb == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_connect(ctx->test_ctx->ldb,
+ ctx->test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ exit(ret);
+ }
+
+ tmp_ctx = talloc_new(ctx->test_ctx);
+ if (tmp_ctx == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ctx->got_cn) {
+ /* Modify the other one */
+ msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb,
+ "cn=test_search_2_cn,"
+ "dc=search_test_entry");
+ } else {
+ msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb,
+ "cn=test_search_cn,"
+ "dc=search_test_entry");
+ }
+ if (msg->dn == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ 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;
+
+ ret = ldb_transaction_start(ctx->test_ctx->ldb);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ if (write(pipes[1], "GO", 2) != 2) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_modify(ctx->test_ctx->ldb, msg);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ ret = ldb_transaction_commit(ctx->test_ctx->ldb);
+ exit(ret);
+ }
+
+ /*
+ * With TDB 1.3.13 and before "tdb: Remove locking from tdb_traverse_read()"
+ * we will hang here because the child process can not proceed to
+ * 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);
+
+ sleep(3);
+
+ return LDB_SUCCESS;
+}
+
+static void test_ldb_modify_during_search(void **state, bool add_index,
+ bool rename)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ struct modify_during_search_test_ctx
+ ctx =
+ { .res_count = 0,
+ .test_ctx = search_test_ctx->ldb_test_ctx,
+ .rename = rename
+ };
+
+ int ret;
+ struct ldb_request *req;
+ pid_t pid;
+ int wstatus;
+
+ if (add_index) {
+ struct ldb_message *msg;
+ struct ldb_dn *indexlist = ldb_dn_new(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "@INDEXLIST");
+ assert_non_null(indexlist);
+
+ msg = ldb_msg_new(search_test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = indexlist;
+
+ 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);
+ }
+
+ tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev);
+
+ ctx.basedn
+ = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "%s",
+ search_test_ctx->base_dn);
+ assert_non_null(ctx.basedn);
+
+
+ /*
+ * This search must be over multiple items, and should include
+ * the new name after a rename, to show that it would match
+ * both before and after that modify
+ */
+ ret = ldb_build_search_req(&req,
+ search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ ctx.basedn,
+ LDB_SCOPE_SUBTREE,
+ "(&(!(filterAttr=*))"
+ "(|(cn=test_search_cn_renamed)"
+ "(cn=test_search_cn)"
+ "(cn=test_search_2_cn)"
+ "))",
+ NULL,
+ NULL,
+ &ctx,
+ test_ldb_modify_during_search_callback1,
+ NULL);
+ assert_int_equal(ret, 0);
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ assert_int_equal(ret, 0);
+ assert_int_equal(ctx.res_count, 2);
+ assert_int_equal(ctx.got_cn, true);
+ assert_int_equal(ctx.got_2_cn, true);
+
+ pid = waitpid(ctx.child_pid, &wstatus, 0);
+ assert_int_equal(pid, ctx.child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+
+}
+
+static void test_ldb_modify_during_indexed_search(void **state)
+{
+ test_ldb_modify_during_search(state, true, false);
+}
+
+static void test_ldb_modify_during_unindexed_search(void **state)
+{
+ test_ldb_modify_during_search(state, false, false);
+}
+
+static void test_ldb_rename_during_indexed_search(void **state)
+{
+ test_ldb_modify_during_search(state, true, true);
+}
+
+static void test_ldb_rename_during_unindexed_search(void **state)
+{
+ test_ldb_modify_during_search(state, false, true);
+}
+
+/*
+ * This test is also complex.
+ *
+ * The purpose is to test if a modify can occur during an ldb_search()
+ * before the end of the callback
+ *
+ * This would be a failure if if in process
+ * (1) and (2):
+ * - (1) ldb_search() starts and calls back for a number of entries
+ * - (2) an entry in the DB is allowed to change before the callback returns
+ * - (1) the callback can see the modification
+ *
+ */
+
+/*
+ * This purpose of this callback is to trigger a write in
+ * the child process while a search DONE callback is in progress.
+ *
+ * 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.
+ */
+
+static int test_ldb_modify_during_whole_search_callback1(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret;
+ int pipes[2];
+ char buf[2];
+ struct modify_during_search_test_ctx *ctx = req->context;
+ struct ldb_dn *search_dn;
+ struct ldb_result *res2;
+ unsigned res_count;
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ break;
+ }
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ ctx->child_pid = fork();
+ if (ctx->child_pid == 0) {
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_message *msg;
+ 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);
+ }
+
+ ctx->test_ctx->ldb = ldb_init(ctx->test_ctx,
+ ctx->test_ctx->ev);
+ if (ctx->test_ctx->ldb == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_connect(ctx->test_ctx->ldb,
+ ctx->test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ exit(ret);
+ }
+
+ tmp_ctx = talloc_new(ctx->test_ctx);
+ if (tmp_ctx == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ msg->dn = ldb_dn_new_fmt(msg, ctx->test_ctx->ldb,
+ "cn=test_search_cn,"
+ "dc=search_test_entry");
+ if (msg->dn == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ 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;
+
+ ret = ldb_transaction_start(ctx->test_ctx->ldb);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ if (write(pipes[1], "GO", 2) != 2) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_modify(ctx->test_ctx->ldb, msg);
+ if (ret != 0) {
+ exit(ret);
+ }
+
+ ret = ldb_transaction_commit(ctx->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 this function, we
+ * will be able to successfully search for this modification
+ * here
+ */
+
+ search_dn = ldb_dn_new_fmt(ares, ctx->test_ctx->ldb,
+ "cn=test_search_cn,"
+ "dc=search_test_entry");
+
+ ret = ldb_search(ctx->test_ctx->ldb, ares,
+ &res2, search_dn, LDB_SCOPE_BASE, NULL,
+ "filterAttr=TRUE");
+
+ /*
+ * We do this in an unusual order, because if we fail an assert before
+ * ldb_request_done(), we will also fail to clean up as we hold locks.
+ */
+
+ res_count = res2->count;
+ ldb_request_done(req, LDB_SUCCESS);
+ assert_int_equal(ret, 0);
+
+ /* We should not have got the result */
+ assert_int_equal(res_count, 0);
+
+ return ret;
+}
+
+static void test_ldb_modify_during_whole_search(void **state)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ struct modify_during_search_test_ctx
+ ctx =
+ {
+ .test_ctx = search_test_ctx->ldb_test_ctx,
+ };
+
+ int ret;
+ struct ldb_request *req;
+ pid_t pid;
+ int wstatus;
+ struct ldb_dn *search_dn;
+ struct ldb_result *res2;
+
+ tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev);
+
+ ctx.basedn
+ = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "%s",
+ search_test_ctx->base_dn);
+ assert_non_null(ctx.basedn);
+
+
+ /*
+ * 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,
+ ctx.basedn,
+ LDB_SCOPE_SUBTREE,
+ "(&(!(filterAttr=*))"
+ "(cn=test_search_cn))",
+ NULL,
+ NULL,
+ &ctx,
+ test_ldb_modify_during_whole_search_callback1,
+ NULL);
+ assert_int_equal(ret, 0);
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ assert_int_equal(ret, 0);
+
+ pid = waitpid(ctx.child_pid, &wstatus, 0);
+ assert_int_equal(pid, ctx.child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+ /*
+ * If writes are blocked until after the search function, 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);
+}
+
+/*
+ * 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.
+ */
+
+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;
+
+ case LDB_REPLY_DONE:
+ break;
+ }
+
+ return ldb_request_done(req, LDB_SUCCESS);
+}
+
+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 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;
+
+ 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);
+
+ 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);
+
+ /*
+ * 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);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ tmp_ctx = talloc_new(search_test_ctx->ldb_test_ctx);
+ if (tmp_ctx == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * 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");
+
+ if (msg->dn == NULL) {
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ 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;
+
+ 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);
+}
+
+/*
+ * This test is also complex.
+ * The purpose is to test if a modify can occur during an ldb_search()
+ * This would be a failure if if in process
+ * (1) and (2):
+ * - (1) ltdb_search() starts and calls back for one entry
+ * - (2) one of the entries to be matched is modified
+ * - (1) the indexed search tries to return the modified entry, but
+ * it is no longer found, either:
+ * - despite it still matching (dn changed)
+ * - it no longer matching (attrs changed)
+ *
+ * We also try un-indexed to show that the behaviour differs on this
+ * point, which it should not (an index should only impact search
+ * speed).
+ */
+
+/*
+ * This purpose of this callback is to trigger a write in the callback
+ * so as to change in in-memory index code while looping over the
+ * index result.
+ */
+
+static int test_ldb_callback_modify_during_search_callback1(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret;
+ struct modify_during_search_test_ctx *ctx = req->context;
+ struct ldb_dn *dn = NULL, *new_dn = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(ctx->test_ctx);
+ struct ldb_message *msg = NULL;
+
+ assert_non_null(tmp_ctx);
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ const struct ldb_val *cn_val
+ = ldb_dn_get_component_val(ares->message->dn, 0);
+ const char *cn = (char *)cn_val->data;
+ ctx->res_count++;
+ if (strcmp(cn, "test_search_cn") == 0) {
+ ctx->got_cn = true;
+ } else if (strcmp(cn, "test_search_2_cn") == 0) {
+ ctx->got_2_cn = true;
+ }
+ if (ctx->res_count == 2) {
+ return LDB_SUCCESS;
+ }
+ break;
+ }
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ if (ctx->rename) {
+ if (ctx->got_2_cn) {
+ /* Modify this one */
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_2_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ } else {
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ }
+ assert_non_null(dn);
+
+ new_dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_cn_renamed,"
+ "dc=not_search_test_entry");
+ assert_non_null(new_dn);
+
+ ret = ldb_rename(ctx->test_ctx->ldb, dn, new_dn);
+ assert_int_equal(ret, 0);
+
+ } else {
+ if (ctx->got_2_cn) {
+ /* Delete this one */
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_2_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ } else {
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ }
+ assert_non_null(dn);
+
+ ret = ldb_delete(ctx->test_ctx->ldb, dn);
+ assert_int_equal(ret, 0);
+ }
+
+ /*
+ * Now fill in the position we just removed from the
+ * index to ensure we fail the test (otherwise we just read
+ * past the end of the array and find the value we wanted to
+ * skip)
+ */
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /* We deliberately use ou= not cn= here */
+ msg->dn = ldb_dn_new_fmt(msg,
+ ctx->test_ctx->ldb,
+ "ou=test_search_cn_extra,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+
+ ret = ldb_msg_add_string(msg,
+ "objectUUID",
+ "0123456789abcde3");
+
+ ret = ldb_add(ctx->test_ctx->ldb,
+ msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ TALLOC_FREE(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static void test_ldb_callback_modify_during_search(void **state, bool add_index,
+ bool rename)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ struct modify_during_search_test_ctx
+ ctx =
+ { .res_count = 0,
+ .test_ctx = search_test_ctx->ldb_test_ctx,
+ .rename = rename
+ };
+
+ int ret;
+ struct ldb_request *req;
+
+ ret = ldb_transaction_start(search_test_ctx->ldb_test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ if (add_index) {
+ struct ldb_message *msg;
+ struct ldb_dn *indexlist = ldb_dn_new(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "@INDEXLIST");
+ assert_non_null(indexlist);
+
+ msg = ldb_msg_new(search_test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = indexlist;
+
+ ret = ldb_msg_add_string(msg, "@IDXONE", "1");
+ assert_int_equal(ret, LDB_SUCCESS);
+ 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;
+ msg->elements[1].flags = LDB_FLAG_MOD_ADD;
+ ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb,
+ msg);
+ }
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Now bring the IDXONE index into memory by modifying
+ * it. This exposes an issue in ldb_tdb
+ */
+ msg = ldb_msg_new(search_test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "cn=test_search_cn_extra,%s",
+ search_test_ctx->base_dn);
+
+ ret = ldb_msg_add_string(msg,
+ "objectUUID",
+ "0123456789abcde2");
+
+ ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb,
+ msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_delete(search_test_ctx->ldb_test_ctx->ldb,
+ msg->dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+
+ tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev);
+
+ ctx.basedn
+ = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "%s",
+ search_test_ctx->base_dn);
+ assert_non_null(ctx.basedn);
+
+
+ /*
+ * This search must be over multiple items, and should include
+ * the new name after a rename, to show that it would match
+ * both before and after that modify
+ *
+ * This needs to be a search that isn't matched by an index so
+ * that we just use the one-level index.
+ */
+ ret = ldb_build_search_req(&req,
+ search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ ctx.basedn,
+ LDB_SCOPE_ONELEVEL,
+ "(cn=*)",
+ NULL,
+ NULL,
+ &ctx,
+ test_ldb_callback_modify_during_search_callback1,
+ NULL);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ assert_int_equal(ret, 0);
+
+ ret = ldb_transaction_commit(search_test_ctx->ldb_test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ assert_int_equal(ctx.res_count, 2);
+ assert_int_equal(ctx.got_cn, true);
+ assert_int_equal(ctx.got_2_cn, true);
+}
+
+static void test_ldb_callback_delete_during_indexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, true, false);
+}
+
+static void test_ldb_callback_delete_during_unindexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, false, false);
+}
+
+static void test_ldb_callback_rename_during_indexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, true, true);
+}
+
+static void test_ldb_callback_rename_during_unindexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, false, true);
+}
+
+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;
+ } else if (ldb_attr_cmp(name, "uid") == 0) {
+ return &cn_attr_2;
+ }
+ return &default_attr;
+}
+
+static void test_ldb_attrs_case_handler(void **state)
+{
+ int cnt;
+ int ret;
+ const struct ldb_schema_syntax *syntax;
+
+ struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ struct ldb_context *ldb = ldb_test_ctx->ldb;
+
+ /* cn matches lower case */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 1);
+
+ 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);
+
+ syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING);
+ assert_non_null(syntax);
+
+ ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb,
+ "cn", 0,
+ syntax, &cn_attr_1);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Set an attribute handler, which will fail to match as we
+ * force case sensitive
+ */
+ ldb_schema_attribute_set_override_handler(ldb,
+ ldb_test_attribute_handler_override,
+ (void *)1);
+
+ /* cn does not matche lower case */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 0);
+
+ syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_DIRECTORY_STRING);
+ assert_non_null(syntax);
+
+ ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb,
+ "cn", 0,
+ syntax, &cn_attr_2);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Set an attribute handler, which will match as we
+ * force case insensitive
+ */
+ ldb_schema_attribute_set_override_handler(ldb,
+ ldb_test_attribute_handler_override,
+ NULL);
+
+ /* cn matches lower case */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 1);
+
+}
+
+
+static void test_ldb_attrs_index_handler(void **state)
+{
+ int cnt;
+ int ret;
+ const struct ldb_schema_syntax *syntax;
+ struct ldb_ldif *ldif;
+
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXATTR: cn\n"
+ "\n";
+
+ struct ldbtest_ctx *ldb_test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ struct ldb_context *ldb = ldb_test_ctx->ldb;
+
+ /* cn matches lower case */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 1);
+
+ syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING);
+ assert_non_null(syntax);
+
+ ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb,
+ "cn", 0,
+ syntax, &cn_attr_1);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_DIRECTORY_STRING);
+ assert_non_null(syntax);
+
+ ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb,
+ "cn", LDB_ATTR_FLAG_INDEXED,
+ 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
+ */
+ ldb_schema_attribute_set_override_handler(ldb,
+ ldb_test_attribute_handler_override,
+ NULL);
+
+ /* cn matches lower case */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 1);
+
+ /* 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);
+ }
+
+ ldb_schema_set_override_indexlist(ldb, false);
+
+ /* cn does match as there is an index now */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 1);
+
+ /*
+ * Set an attribute handler, which will later fail to match as we
+ * didn't re-index the DB
+ */
+ ldb_schema_attribute_set_override_handler(ldb,
+ ldb_test_attribute_handler_override,
+ (void *)1);
+
+ /*
+ * cn does not match as we changed the case sensitivity, but
+ * didn't re-index
+ *
+ * This shows that the override is in control
+ */
+ cnt = sub_search_count(ldb_test_ctx, "", "cn=caseinsensitivevalue");
+ assert_int_equal(cnt, 0);
+
+}
+
+static int ldb_case_attrs_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");
+
+ ldb_case_test_teardown(state);
+ return 0;
+}
+
+
+struct rename_test_ctx {
+ struct ldbtest_ctx *ldb_test_ctx;
+
+ struct ldb_dn *basedn;
+ const char *str_basedn;
+
+ const char *teardown_dn;
+};
+
+static int ldb_rename_test_setup(void **state)
+{
+ struct ldbtest_ctx *ldb_test_ctx;
+ struct rename_test_ctx *rename_test_ctx;
+ const char *strdn = "dc=rename_test_entry_from";
+
+ ldbtest_setup((void **) &ldb_test_ctx);
+
+ rename_test_ctx = talloc(ldb_test_ctx, struct rename_test_ctx);
+ assert_non_null(rename_test_ctx);
+ rename_test_ctx->ldb_test_ctx = ldb_test_ctx;
+ assert_non_null(rename_test_ctx->ldb_test_ctx);
+
+ rename_test_ctx->basedn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", strdn);
+ assert_non_null(rename_test_ctx->basedn);
+
+ rename_test_ctx->str_basedn = strdn;
+ rename_test_ctx->teardown_dn = strdn;
+
+ add_dn_with_cn(ldb_test_ctx,
+ rename_test_ctx->basedn,
+ "test_rename_cn_val",
+ "0123456789abcde0");
+
+ *state = rename_test_ctx;
+ return 0;
+}
+
+static int ldb_rename_test_teardown(void **state)
+{
+ int ret;
+ struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort(*state,
+ struct rename_test_ctx);
+ struct ldbtest_ctx *ldb_test_ctx;
+ struct ldb_dn *del_dn;
+
+ ldb_test_ctx = rename_test_ctx->ldb_test_ctx;
+
+ del_dn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", rename_test_ctx->teardown_dn);
+ 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,
+ rename_test_ctx->teardown_dn);
+
+ ldbtest_teardown((void **) &ldb_test_ctx);
+ return 0;
+}
+
+static void test_ldb_rename(void **state)
+{
+ struct rename_test_ctx *rename_test_ctx =
+ talloc_get_type_abort(*state, struct rename_test_ctx);
+ int ret;
+ const char *str_new_dn = "dc=rename_test_entry_to";
+ struct ldb_dn *new_dn;
+
+ new_dn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", str_new_dn);
+ assert_non_null(new_dn);
+
+ ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
+ rename_test_ctx->basedn,
+ new_dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_dn_exists(rename_test_ctx->ldb_test_ctx, str_new_dn);
+ assert_dn_doesnt_exist(rename_test_ctx->ldb_test_ctx,
+ rename_test_ctx->str_basedn);
+ rename_test_ctx->teardown_dn = str_new_dn;
+
+ /* FIXME - test the values which didn't change */
+}
+
+static void test_ldb_rename_from_doesnt_exist(void **state)
+{
+ struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort(
+ *state,
+ struct rename_test_ctx);
+ int ret;
+ const char *str_new_dn = "dc=rename_test_entry_to";
+ const char *str_bad_old_dn = "dc=rename_test_no_such_entry";
+ struct ldb_dn *new_dn;
+ struct ldb_dn *bad_old_dn;
+
+ new_dn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", str_new_dn);
+ assert_non_null(new_dn);
+
+ bad_old_dn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", str_bad_old_dn);
+ assert_non_null(bad_old_dn);
+
+ assert_dn_doesnt_exist(rename_test_ctx->ldb_test_ctx,
+ str_bad_old_dn);
+
+ ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
+ bad_old_dn, new_dn);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+
+ assert_dn_doesnt_exist(rename_test_ctx->ldb_test_ctx,
+ str_new_dn);
+}
+
+static void test_ldb_rename_to_exists(void **state)
+{
+ struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort(
+ *state,
+ struct rename_test_ctx);
+ int ret;
+ const char *str_new_dn = "dc=rename_test_already_exists";
+ struct ldb_dn *new_dn;
+
+ new_dn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", str_new_dn);
+ assert_non_null(new_dn);
+
+ add_dn_with_cn(rename_test_ctx->ldb_test_ctx,
+ new_dn,
+ "test_rename_cn_val",
+ "0123456789abcde1");
+
+ ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
+ rename_test_ctx->basedn,
+ new_dn);
+ assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS);
+
+ /* Old object must still exist */
+ assert_dn_exists(rename_test_ctx->ldb_test_ctx,
+ rename_test_ctx->str_basedn);
+
+ ret = ldb_delete(rename_test_ctx->ldb_test_ctx->ldb,
+ new_dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_dn_exists(rename_test_ctx->ldb_test_ctx,
+ rename_test_ctx->teardown_dn);
+}
+
+static void test_ldb_rename_self(void **state)
+{
+ struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort(
+ *state,
+ struct rename_test_ctx);
+ int ret;
+
+ /* Oddly enough, this is a success in ldb.. */
+ ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
+ rename_test_ctx->basedn,
+ rename_test_ctx->basedn);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /* Old object must still exist */
+ assert_dn_exists(rename_test_ctx->ldb_test_ctx,
+ rename_test_ctx->str_basedn);
+}
+
+static void test_ldb_rename_dn_case_change(void **state)
+{
+ struct rename_test_ctx *rename_test_ctx = talloc_get_type_abort(
+ *state,
+ struct rename_test_ctx);
+ int ret;
+ char *str_new_dn;
+ struct ldb_dn *new_dn;
+ unsigned i;
+
+ str_new_dn = talloc_strdup(rename_test_ctx, rename_test_ctx->str_basedn);
+ assert_non_null(str_new_dn);
+ for (i = 0; str_new_dn[i]; i++) {
+ str_new_dn[i] = toupper(str_new_dn[i]);
+ }
+
+ new_dn = ldb_dn_new_fmt(rename_test_ctx,
+ rename_test_ctx->ldb_test_ctx->ldb,
+ "%s", str_new_dn);
+ assert_non_null(new_dn);
+
+ ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
+ rename_test_ctx->basedn,
+ new_dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /* DNs are case insensitive, so both searches will match */
+ assert_dn_exists(rename_test_ctx->ldb_test_ctx, str_new_dn);
+ assert_dn_exists(rename_test_ctx->ldb_test_ctx,
+ rename_test_ctx->str_basedn);
+ /* FIXME - test the values didn't change */
+}
+
+static int ldb_read_only_setup(void **state)
+{
+ 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 opened 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 PRINTF_ATTRIBUTE(3, 0) ldb_debug_string(
+ void *context,
+ enum ldb_debug_level level,
+ const char *fmt, va_list ap)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(context, struct ldbtest_ctx);
+
+ if (level <= LDB_DEBUG_WARNING) {
+ test_ctx->debug_string = talloc_vasprintf(test_ctx, 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 *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, test_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");
+
+ 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(test_ctx->debug_string);
+ p = strstr(
+ test_ctx->debug_string,
+ "unique index violation on cn "
+ "in dc=test02, conflicts with dc=test01 in "
+ "@INDEX:CN:test_unique_index");
+ assert_non_null(p);
+ TALLOC_FREE(test_ctx->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;
+
+ /* The GUID mode is not compatible with this test */
+#ifdef GUID_IDX
+ return;
+#endif
+
+ ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_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_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(test_ctx->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 *p = NULL;
+
+ ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_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", "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=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(test_ctx->debug_string);
+ p = strstr(
+ test_ctx->debug_string,
+ "unique index violation on cn in dc=test02, conflicts with "
+ "objectUUID 0123456789abcdef in @INDEX:CN:test_unique_index");
+ assert_non_null(p);
+ TALLOC_FREE(test_ctx->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;
+
+ ldb_set_debug(test_ctx->ldb, ldb_debug_string, test_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_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(test_ctx->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);
+ }
+}
+
+#ifdef TEST_LMDB
+static int test_ldb_multiple_connections_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret;
+ int pipes[2];
+ char buf[2];
+ int pid, child_pid;
+ int wstatus;
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ {
+ /*
+ * We open a new ldb on an ldb that is already open and
+ * then close it.
+ *
+ * If the multiple connection wrapping is correct the
+ * underlying MDB_env will be left open and we should see
+ * an active reader in the child we fork next
+ */
+ struct ldb_context *ldb = NULL;
+ struct tevent_context *ev = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert_non_null(ev);
+
+ ldb = ldb_init(mem_ctx, ev);
+ assert_non_null(ldb);
+
+ ret = ldb_connect(ldb, TEST_BE"://apitest.ldb" , 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ TALLOC_FREE(ldb);
+ TALLOC_FREE(mem_ctx);
+ }
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct MDB_env *env = NULL;
+ struct MDB_envinfo stat;
+ close(pipes[0]);
+
+ /*
+ * Check that there are exactly two readers on the MDB file
+ * backing the ldb.
+ *
+ */
+ ret = mdb_env_create(&env);
+ if (ret != 0) {
+ print_error(__location__
+ " mdb_env_create returned (%d)",
+ ret);
+ exit(ret);
+ }
+
+ ret = mdb_env_open(env,
+ "apitest.ldb",
+ MDB_NOSUBDIR | MDB_NOTLS,
+ 0644);
+ if (ret != 0) {
+ print_error(__location__
+ " mdb_env_open returned (%d)",
+ ret);
+ exit(ret);
+ }
+
+ ret = mdb_env_info(env, &stat);
+ if (ret != 0) {
+ print_error(__location__
+ " mdb_env_info returned (%d)",
+ ret);
+ exit(ret);
+ }
+ if (stat.me_numreaders != 2) {
+ print_error(__location__
+ " Incorrect number of readers (%d)",
+ stat.me_numreaders);
+ exit(LDB_ERR_CONSTRAINT_VIOLATION);
+ }
+
+ 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);
+ return LDB_SUCCESS;
+
+}
+
+static void test_ldb_close_with_multiple_connections(void **state)
+{
+ struct search_test_ctx *search_test_ctx = NULL;
+ struct ldb_dn *search_dn = NULL;
+ struct ldb_request *req = NULL;
+ int ret = 0;
+
+ search_test_ctx = talloc_get_type_abort(*state, struct search_test_ctx);
+ assert_non_null(search_test_ctx);
+
+ 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);
+
+ /*
+ * 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,
+ search_dn,
+ LDB_SCOPE_SUBTREE,
+ "(&(!(filterAttr=*))"
+ "(cn=test_search_cn))",
+ NULL,
+ NULL,
+ NULL,
+ test_ldb_multiple_connections_callback,
+ NULL);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ assert_int_equal(ret, 0);
+}
+
+#endif
+
+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_callback_rename_during_unindexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_rename_during_indexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_delete_during_unindexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_delete_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,
+ ldb_case_test_setup,
+ ldb_case_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_attrs_case_handler,
+ ldb_case_test_setup,
+ ldb_case_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_attrs_index_handler,
+ ldb_case_test_setup,
+ ldb_case_attrs_index_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_rename,
+ ldb_rename_test_setup,
+ ldb_rename_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_rename_from_doesnt_exist,
+ ldb_rename_test_setup,
+ ldb_rename_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_rename_to_exists,
+ ldb_rename_test_setup,
+ ldb_rename_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_rename_self,
+ ldb_rename_test_setup,
+ ldb_rename_test_teardown),
+ 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),
+#ifdef TEST_LMDB
+ cmocka_unit_test_setup_teardown(
+ test_ldb_close_with_multiple_connections,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+#endif
+ 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),
+ };
+
+ if (argc > 1) {
+ cmocka_set_test_filter(argv[1]);
+ }
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_msg.c b/lib/ldb/tests/ldb_msg.c
new file mode 100644
index 0000000..31786a9
--- /dev/null
+++ b/lib/ldb/tests/ldb_msg.c
@@ -0,0 +1,380 @@
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+
+#include <ldb.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+struct test_ctx {
+ struct ldb_message *msg;
+};
+
+static int ldb_msg_setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->msg = ldb_msg_new(test_ctx);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldb_msg_teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ talloc_free(test_ctx);
+ return 0;
+}
+
+
+static void add_uint_value(struct test_ctx *test_ctx,
+ struct ldb_message *msg,
+ const char *attr,
+ unsigned int x)
+{
+ int ret;
+ struct ldb_val v, v_dup;
+ char s[5];
+ snprintf(s, sizeof(s), "%04x", x);
+ v.data = (uint8_t *)s;
+ v.length = 4;
+ v_dup = ldb_val_dup(test_ctx, &v);
+ assert_non_null(v_dup.data);
+ assert_ptr_not_equal(v_dup.data, v.data);
+ assert_int_equal(v_dup.length, 4);
+
+ ret = ldb_msg_add_value(msg, attr, &v_dup, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+}
+
+
+static void test_ldb_msg_find_duplicate_val(void **state)
+{
+ int ret;
+ unsigned int i;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_message *msg = test_ctx->msg;
+ struct ldb_message_element *el;
+ struct ldb_val dummy;
+ struct ldb_val *dupe = &dummy; /* so we can tell it was modified to NULL, not left as NULL */
+
+ ret = ldb_msg_add_empty(msg, "el1", 0, &el);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /* An empty message contains no duplicates */
+ ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_null(dupe);
+
+ for (i = 0; i < 5; i++) {
+ add_uint_value(test_ctx, msg, "el1", i);
+ }
+ /* at this point there are no duplicates, and the check uses the naive
+ quadratic path */
+ ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_null(dupe);
+
+ /* add a duplicate, still using quadratric path */
+ add_uint_value(test_ctx, msg, "el1", 3);
+ ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(dupe);
+ assert_int_equal(dupe->length, 4);
+ assert_memory_equal(dupe->data, "0003", 4);
+
+ /* add some more, triggering algorithmic jump */
+ for (i = 2; i < 11; i++) {
+ add_uint_value(test_ctx, msg, "el1", i);
+ }
+ ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_non_null(dupe);
+ assert_int_equal(dupe->length, 4);
+ /*XXX not really guaranteed by the API */
+ assert_memory_equal(dupe->data, "0002", 4);
+
+ /* start a new element without duplicates, for the clever algorithm */
+ ldb_msg_add_empty(msg, "el2", 0, &el);
+ for (i = 0; i < 12; i++) {
+ add_uint_value(test_ctx, msg, "el2", i);
+ }
+ ret = ldb_msg_find_duplicate_val(NULL, test_ctx, el, &dupe, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_null(dupe);
+}
+
+
+static struct ldb_message_element *new_msg_element(TALLOC_CTX *mem_ctx,
+ const char *name,
+ unsigned int value_offset,
+ unsigned int num_values)
+{
+ unsigned int i, x;
+ struct ldb_message_element *el = talloc_zero(mem_ctx,
+ struct ldb_message_element);
+
+ el->values = talloc_array(el, struct ldb_val, num_values);
+ for (i = 0; i < num_values; i++) {
+ struct ldb_val v;
+ char s[50];
+ v.data = (uint8_t *)s;
+ /* % 3 is to ensure the values list is unsorted */
+ x = i + value_offset;
+ v.length = snprintf(s, sizeof(s), "%u %u", x % 3, x);
+ el->values[i] = ldb_val_dup(mem_ctx, &v);
+ }
+ el->name = name;
+ el->num_values = num_values;
+ return el;
+}
+
+static void _assert_element_equal(struct ldb_message_element *a,
+ struct ldb_message_element *b,
+ const char * const file,
+ const int line)
+{
+ unsigned int i;
+ _assert_int_equal(a->num_values, b->num_values, file, line);
+ _assert_int_equal(a->flags, b->flags, file, line);
+ _assert_string_equal(a->name, b->name, file, line);
+ for (i = 0; i < a->num_values; i++) {
+ struct ldb_val *v1 = &a->values[i];
+ struct ldb_val *v2 = &b->values[i];
+ _assert_int_equal(v1->length, v2->length, file, line);
+ _assert_memory_equal(v1->data, v2->data, v1->length,
+ file, line);
+ }
+}
+
+#define assert_element_equal(a, b) \
+ _assert_element_equal((a), (b), \
+ __FILE__, __LINE__)
+
+
+static void test_ldb_msg_find_common_values(void **state)
+{
+ /* we only use the state as a talloc context */
+ struct ldb_message_element *el, *el2, *el3, *el4, *el2b, *empty;
+ struct ldb_message_element *orig, *orig2, *orig3, *orig4;
+ int ret;
+ const uint32_t remove_dupes = LDB_MSG_FIND_COMMON_REMOVE_DUPLICATES;
+ el = new_msg_element(*state, "test", 0, 4);
+ el2 = new_msg_element(*state, "test", 4, 4);
+ el3 = new_msg_element(*state, "test", 6, 4);
+ empty = new_msg_element(*state, "test", 0, 0);
+ orig = new_msg_element(*state, "test", 0, 4);
+ orig2 = new_msg_element(*state, "test", 4, 4);
+ orig3 = new_msg_element(*state, "test", 6, 4);
+
+ /* first round is with short value arrays, using quadratic method */
+ /* we expect no collisions here */
+ ret = ldb_msg_find_common_values(NULL, *state, el, el2, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*or here */
+ ret = ldb_msg_find_common_values(NULL, *state, el, el3, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /* the same elements in reverse order */
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /* 6, 7 collide */
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el3, 0);
+ assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+
+ /* and again */
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el2, 0);
+ assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+
+ /* make sure the arrays haven't changed */
+ assert_element_equal(el, orig);
+ assert_element_equal(el2, orig2);
+ assert_element_equal(el3, orig3);
+
+ /* now with the control permisive flag, the first element should be
+ modified to remove the overlap.*/
+
+ /* 6, 7 collide, so el2 will only have 4 and 5 */
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el3, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_element_equal(el3, orig3);
+ assert_int_not_equal(el2->num_values, orig2->num_values);
+ assert_int_equal(el2->num_values, 2);
+ el2b = new_msg_element(*state, "test", 4, 2);
+ assert_element_equal(el2, el2b);
+
+ /* now try the same things with a long and a short value list.
+ this should still trigger the quadratic path.
+ */
+ el2 = new_msg_element(*state, "test", 4, 10);
+ orig2 = new_msg_element(*state, "test", 4, 10);
+
+ /* no collisions */
+ ret = ldb_msg_find_common_values(NULL, *state, el, el2, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*collisions */
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el2, 0);
+ assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+
+ assert_element_equal(el, orig);
+ assert_element_equal(el2, orig2);
+ assert_element_equal(el3, orig3);
+
+ /*collisions with permissive flag*/
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el2, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_element_equal(el2, orig2);
+ assert_int_equal(el3->num_values, 0);
+
+ /* permutations involving empty elements.
+ everything should succeed. */
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el2, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el3, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(el2->num_values, orig2->num_values);
+ ret = ldb_msg_find_common_values(NULL, *state, el3, el2, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(el2->num_values, orig2->num_values);
+ assert_int_equal(el3->num_values, 0); /* el3 is now empty */
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el3, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, el3, empty, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, empty, empty, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, empty, el3, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_element_equal(el2, orig2);
+ assert_element_equal(el, orig);
+ assert_int_equal(el3->num_values, 0);
+
+ /* now with two large value lists */
+ el = new_msg_element(*state, "test", 0, 12);
+ orig = new_msg_element(*state, "test", 0, 12);
+ el4 = new_msg_element(*state, "test", 12, 12);
+ orig4 = new_msg_element(*state, "test", 12, 12);
+
+ /* no collisions */
+ ret = ldb_msg_find_common_values(NULL, *state, el, el4, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_msg_find_common_values(NULL, *state, el4, el, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /* collisions */
+ ret = ldb_msg_find_common_values(NULL, *state, el4, el2, 0);
+ assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el4, 0);
+ assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el, 0);
+ assert_int_equal(ret, LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+
+ assert_element_equal(el, orig);
+ assert_element_equal(el2, orig2);
+ assert_element_equal(el4, orig4);
+
+ /* with permissive control, but no collisions */
+ ret = ldb_msg_find_common_values(NULL, *state, el, el4, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, el4, el, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ assert_element_equal(el, orig);
+ assert_element_equal(el4, orig4);
+
+ /* now with collisions, thus modifications.
+ At this stage:
+ el is 0-11 (inclusive)
+ e2 is 4-13
+ el3 is empty
+ el4 is 12-23
+ */
+ ret = ldb_msg_find_common_values(NULL, *state, el4, el2, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_element_equal(el2, orig2);
+ assert_int_not_equal(el4->num_values, orig4->num_values);
+ /* 4 should start at 14 */
+ orig4 = new_msg_element(*state, "test", 14, 10);
+ assert_element_equal(el4, orig4);
+
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_element_equal(el, orig);
+ assert_int_not_equal(el2->num_values, orig2->num_values);
+ orig2 = new_msg_element(*state, "test", 12, 2);
+ assert_element_equal(el2, orig2);
+
+ /* test the empty el against the full elements */
+ ret = ldb_msg_find_common_values(NULL, *state, el, empty, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, empty, el, 0);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, el, empty, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_find_common_values(NULL, *state, empty, el, remove_dupes);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_element_equal(el, orig);
+ assert_element_equal(empty, el3);
+
+ /* make sure an identical element with a different name is rejected */
+ el2 = new_msg_element(*state, "fish", 12, 2);
+ ret = ldb_msg_find_common_values(NULL, *state, el2, el, remove_dupes);
+ assert_int_equal(ret, LDB_ERR_INAPPROPRIATE_MATCHING);
+}
+
+
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_ldb_msg_find_duplicate_val,
+ ldb_msg_setup,
+ ldb_msg_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_msg_find_common_values,
+ ldb_msg_setup,
+ ldb_msg_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_no_lmdb_test.c b/lib/ldb/tests/ldb_no_lmdb_test.c
new file mode 100644
index 0000000..be23d74
--- /dev/null
+++ b/lib/ldb/tests/ldb_no_lmdb_test.c
@@ -0,0 +1,159 @@
+/*
+ * Ensure lmdb backend is disabled
+ *
+ * Copyright (C) Mathieu Parent <math.parent@gmail.com> 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * Ensure lmdb backend is disabled
+ *
+ * Setup and tear down code copied from ldb_lmdb_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+
+#define TEST_BE "mdb"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, LDB_ERR_OTHER);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static void test_ldb_lmdb_not_found(void **state)
+{
+ // Actual test in ldbtest_setup
+ assert_int_equal(0, 0);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_ldb_lmdb_not_found,
+ ldbtest_setup,
+ ldbtest_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_parse_test.c b/lib/ldb/tests/ldb_parse_test.c
new file mode 100644
index 0000000..b08c7b7
--- /dev/null
+++ b/lib/ldb/tests/ldb_parse_test.c
@@ -0,0 +1,172 @@
+/*
+ * Tests exercising the ldb parse operations.
+ *
+ * Copyright (C) Catalyst.NET Ltd 2017
+ * Copyright (C) Michael Hanselmann 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "../include/ldb.h"
+
+struct test_ctx { uint8_t dummy; };
+
+static int setup(void **state)
+{
+ struct test_ctx *ctx;
+
+ ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(ctx);
+
+ *state = ctx;
+
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ struct test_ctx *ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+
+ talloc_free(ctx);
+
+ return 0;
+}
+
+static void test_roundtrip(TALLOC_CTX *mem_ctx, const char *filter, const char *expected)
+{
+ struct ldb_parse_tree *tree;
+ char *serialized;
+
+ assert_non_null(filter);
+ assert_non_null(expected);
+
+ tree = ldb_parse_tree(mem_ctx, filter);
+ assert_non_null(tree);
+
+ serialized = ldb_filter_from_tree(mem_ctx, tree);
+ assert_non_null(serialized);
+
+ assert_string_equal(serialized, expected);
+}
+
+static void test_parse_filtertype(void **state)
+{
+ struct test_ctx *ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+
+ test_roundtrip(ctx, "", "(|(objectClass=*)(distinguishedName=*))");
+ test_roundtrip(ctx, "a=value", "(a=value)");
+ test_roundtrip(ctx, "(|(foo=bar)(baz=hello))", "(|(foo=bar)(baz=hello))");
+ test_roundtrip(ctx, " ", "(|(objectClass=*)(distinguishedName=*))");
+}
+
+/*
+ * Test that a nested query with 128 levels of nesting is accepted
+ */
+static void test_nested_filter_eq_limit(void **state)
+{
+ struct test_ctx *ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+
+ /*
+ * 128 nested clauses
+ */
+ const char *nested_query = ""
+ "(|(!(|(&(|(|(|(|(|(|(|(|(|(|(|(|"
+ "(|(!(|(&(|(|(|(|(|(|(!(|(!(|(|(|"
+ "(|(!(|(&(|(|(&(|(|(|(|(|(!(!(!(|"
+ "(|(!(|(&(|(|(|(|(|(|(|(|(|(|(|(|"
+ "(|(!(|(&(|(|(|(!(|(|(&(|(|(|(|(|"
+ "(|(!(|(&(|(|(&(|(|(|(|(|(&(&(|(|"
+ "(|(!(|(&(|(|(|(|(|(|(!(|(|(|(|(|"
+ "(|(!(|(&(|(|(!(|(|(|(|(|(|(|(|(|"
+ "(a=b)"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))";
+
+ struct ldb_parse_tree *tree = ldb_parse_tree(ctx, nested_query);
+
+ assert_non_null(tree);
+ /*
+ * Check that we get the same query back
+ */
+ test_roundtrip(ctx, nested_query, nested_query);
+}
+
+/*
+ * Test that a nested query with 129 levels of nesting is rejected.
+ */
+static void test_nested_filter_gt_limit(void **state)
+{
+ struct test_ctx *ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+
+ /*
+ * 129 nested clauses
+ */
+ const char *nested_query = ""
+ "(|(!(|(|(&(|(|(|(|(&(|(|(|(|(|(|"
+ "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|"
+ "(|(!(|(|(&(|(|(!(|(|(|(|(!(|(|(|"
+ "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|"
+ "(|(!(|(|(&(|(|(|(!(&(|(|(|(|(|(|"
+ "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|"
+ "(|(!(|(|(&(|(|(|(|(|(|(|(|(|(|(|"
+ "(|(!(|(|(&(|(|(|(|(|(|(|(|(&(|(|"
+ "(|"
+ "(a=b)"
+ ")"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))"
+ "))))))))))))))))";
+
+ struct ldb_parse_tree *tree = ldb_parse_tree(ctx, nested_query);
+
+ assert_null(tree);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_parse_filtertype, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_nested_filter_eq_limit, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_nested_filter_gt_limit, setup, teardown),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/ldb_tdb_test.c b/lib/ldb/tests/ldb_tdb_test.c
new file mode 100644
index 0000000..64e5983
--- /dev/null
+++ b/lib/ldb/tests/ldb_tdb_test.c
@@ -0,0 +1,389 @@
+/*
+ * tdb backend specific tests for ldb
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * tdb backend specific tests for ldb
+ *
+ * Setup and tear down code copied from ldb_mod_op_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "../ldb_tdb/ldb_tdb.h"
+#include "../ldb_key_value/ldb_kv.h"
+
+#define TEST_BE "tdb"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ 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;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+
+static TDB_CONTEXT *get_tdb_context(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ldb_kv_private *ldb_kv = NULL;
+ TDB_CONTEXT *tdb = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ldb_kv = talloc_get_type(data, struct ldb_kv_private);
+ assert_non_null(ldb_kv);
+
+ tdb = ldb_kv->tdb;
+ assert_non_null(tdb);
+
+ return tdb;
+}
+
+static void test_multiple_opens(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ struct ldb_context *ldb3 = NULL;
+ TDB_CONTEXT *tdb1 = NULL;
+ TDB_CONTEXT *tdb2 = NULL;
+ TDB_CONTEXT *tdb3 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+
+ 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, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+ /*
+ * We now have 3 ldb's open pointing to the same on disk database
+ * they should all share the same MDB_env
+ */
+ tdb1 = get_tdb_context(ldb1);
+ tdb2 = get_tdb_context(ldb2);
+ tdb3 = get_tdb_context(ldb3);
+
+ assert_ptr_equal(tdb1, tdb2);
+ assert_ptr_equal(tdb1, tdb3);
+}
+
+static void test_multiple_opens_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ TDB_CONTEXT *tdb1 = NULL;
+ TDB_CONTEXT *tdb2 = 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, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ tdb1 = get_tdb_context(ldb1);
+ tdb2 = get_tdb_context(ldb2);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_context *ldb3 = NULL;
+ TDB_CONTEXT *tdb3 = 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);
+ }
+ tdb3 = get_tdb_context(ldb3);
+ if (tdb1 != tdb2) {
+ print_error(__location__": tdb1 != tdb2\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (tdb1 != tdb3) {
+ print_error(__location__": tdb1 != tdb3\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ 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_multiple_opens_across_fork_triggers_reopen(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ TDB_CONTEXT *tdb1 = NULL;
+ TDB_CONTEXT *tdb2 = 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, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ tdb1 = get_tdb_context(ldb1);
+ tdb2 = get_tdb_context(ldb2);
+ assert_ptr_equal(tdb1, tdb2);
+
+ /*
+ * Break the internal tdb_reopen() by making a
+ * transaction
+ *
+ * This shows that the tdb_reopen() is called, which is
+ * essential if the host OS does not have pread()
+ */
+ ret = tdb_transaction_start(tdb1);
+ 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);
+
+ /*
+ * This should fail as we have taken out a lock
+ * against the raw TDB above, and tdb_reopen()
+ * will fail in that state.
+ *
+ * This check matters as tdb_reopen() is important
+ * if the host does not have pread()
+ */
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ if (ret == 0) {
+ print_error(__location__": ldb_connect expected "
+ "LDB_ERR_OPERATIONS_ERROR "
+ "returned (%d)\n",
+ ret);
+ exit(5000);
+ }
+ 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_multiple_opens,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens_across_fork_triggers_reopen,
+ ldbtest_setup,
+ ldbtest_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/lldb_ldap.c b/lib/ldb/tests/lldb_ldap.c
new file mode 100644
index 0000000..eea9f22
--- /dev/null
+++ b/lib/ldb/tests/lldb_ldap.c
@@ -0,0 +1,105 @@
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+
+#include <ldb.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+int ldb_ldap_init(const char *version);
+
+#include "ldb_ldap/ldb_ldap.c"
+
+struct test_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+};
+
+static int lldb_msg_setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->msg = ldb_msg_new(test_ctx);
+ assert_non_null(test_ctx->msg);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int lldb_msg_teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static void test_lldb_add_msg_attr(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ldb_message *msg = test_ctx->msg;
+ int ret;
+ unsigned int num_elements = 0;
+ struct berval **v = NULL;
+
+ v = talloc_zero_array(test_ctx, struct berval *, 2);
+ assert_non_null(v);
+
+ v[0] = talloc_zero(v, struct berval);
+ assert_non_null(v[0]);
+
+ v[0]->bv_val = talloc_strdup(msg, "dc=example,dc=test");
+ assert_non_null(v[0]->bv_val);
+
+ v[0]->bv_len = strlen(v[0]->bv_val);
+
+ num_elements = msg->num_elements;
+
+ ret = lldb_add_msg_attr(test_ctx->ldb, msg, "defaultNamingContext", v);
+ assert_int_equal(ret, LDB_SUCCESS);
+ assert_int_equal(msg->num_elements, num_elements + 1);
+}
+
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_lldb_add_msg_attr,
+ lldb_msg_setup,
+ lldb_msg_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/photo.ldif b/lib/ldb/tests/photo.ldif
new file mode 100644
index 0000000..95ab065
--- /dev/null
+++ b/lib/ldb/tests/photo.ldif
@@ -0,0 +1,5 @@
+dn: cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+changetype: modify
+add: jpegPhoto
+jpegPhoto:< file://tests/samba4.png
+
diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py
new file mode 100755
index 0000000..043b721
--- /dev/null
+++ b/lib/ldb/tests/python/api.py
@@ -0,0 +1,3858 @@
+#!/usr/bin/env python3
+# Simple tests for the ldb python bindings.
+# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
+
+import os
+from unittest import TestCase
+import sys
+sys.path.insert(0, "bin/python")
+import gc
+import time
+import ldb
+import shutil
+import errno
+
+
+TDB_PREFIX = "tdb://"
+MDB_PREFIX = "mdb://"
+
+MDB_INDEX_OBJ = {
+ "dn": "@INDEXLIST",
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]
+}
+
+
+def tempdir():
+ import tempfile
+ try:
+ dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp")
+ except KeyError:
+ dir_prefix = None
+ return tempfile.mkdtemp(dir=dir_prefix)
+
+
+class NoContextTests(TestCase):
+
+ def test_valid_attr_name(self):
+ self.assertTrue(ldb.valid_attr_name("foo"))
+ self.assertFalse(ldb.valid_attr_name("24foo"))
+
+ def test_timestring(self):
+ self.assertEqual("19700101000000.0Z", ldb.timestring(0))
+ self.assertEqual("20071119191012.0Z", ldb.timestring(1195499412))
+
+ self.assertEqual("00000101000000.0Z", ldb.timestring(-62167219200))
+ self.assertEqual("99991231235959.0Z", ldb.timestring(253402300799))
+
+ # should result with OSError EOVERFLOW from gmtime()
+ with self.assertRaises(OSError) as err:
+ ldb.timestring(-62167219201)
+ self.assertEqual(err.exception.errno, errno.EOVERFLOW)
+ with self.assertRaises(OSError) as err:
+ ldb.timestring(253402300800)
+ self.assertEqual(err.exception.errno, errno.EOVERFLOW)
+ with self.assertRaises(OSError) as err:
+ ldb.timestring(0x7fffffffffffffff)
+ self.assertEqual(err.exception.errno, errno.EOVERFLOW)
+
+ def test_string_to_time(self):
+ self.assertEqual(0, ldb.string_to_time("19700101000000.0Z"))
+ self.assertEqual(1195499412, ldb.string_to_time("20071119191012.0Z"))
+
+ self.assertEqual(-62167219200, ldb.string_to_time("00000101000000.0Z"))
+ self.assertEqual(253402300799, ldb.string_to_time("99991231235959.0Z"))
+
+ def test_binary_encode(self):
+ encoded = ldb.binary_encode(b'test\\x')
+ decoded = ldb.binary_decode(encoded)
+ self.assertEqual(decoded, b'test\\x')
+
+ encoded2 = ldb.binary_encode('test\\x')
+ self.assertEqual(encoded2, encoded)
+
+
+class LdbBaseTest(TestCase):
+ def setUp(self):
+ super(LdbBaseTest, self).setUp()
+ try:
+ if self.prefix is None:
+ self.prefix = TDB_PREFIX
+ except AttributeError:
+ self.prefix = TDB_PREFIX
+
+ def tearDown(self):
+ super(LdbBaseTest, self).tearDown()
+
+ def url(self):
+ return self.prefix + self.filename
+
+ def flags(self):
+ if self.prefix == MDB_PREFIX:
+ return ldb.FLG_NOSYNC
+ else:
+ return 0
+
+
+class SimpleLdb(LdbBaseTest):
+
+ def setUp(self):
+ super(SimpleLdb, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ self.ldb = ldb.Ldb(self.url(), flags=self.flags())
+ try:
+ self.ldb.add(self.index)
+ except AttributeError:
+ pass
+
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(SimpleLdb, self).tearDown()
+ # Ensure the LDB is closed now, so we close the FD
+ del(self.ldb)
+
+ def test_connect(self):
+ ldb.Ldb(self.url(), flags=self.flags())
+
+ def test_connect_none(self):
+ ldb.Ldb()
+
+ def test_connect_later(self):
+ x = ldb.Ldb()
+ x.connect(self.url(), flags=self.flags())
+
+ def test_repr(self):
+ x = ldb.Ldb()
+ self.assertTrue(repr(x).startswith("<ldb connection"))
+
+ def test_set_create_perms(self):
+ x = ldb.Ldb()
+ x.set_create_perms(0o600)
+
+ def test_modules_none(self):
+ x = ldb.Ldb()
+ self.assertEqual([], x.modules())
+
+ def test_modules_tdb(self):
+ x = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual("[<ldb module 'tdb'>]", repr(x.modules()))
+
+ def test_firstmodule_none(self):
+ x = ldb.Ldb()
+ self.assertEqual(x.firstmodule, None)
+
+ def test_firstmodule_tdb(self):
+ x = ldb.Ldb(self.url(), flags=self.flags())
+ mod = x.firstmodule
+ self.assertEqual(repr(mod), "<ldb module 'tdb'>")
+
+ def test_search(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(len(l.search()), 0)
+
+ def test_search_controls(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(len(l.search(controls=["paged_results:0:5"])), 0)
+
+ def test_utf8_ldb_Dn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ dn = ldb.Dn(l, (b'a=' + b'\xc4\x85\xc4\x87\xc4\x99\xc5\x82\xc5\x84\xc3\xb3\xc5\x9b\xc5\xba\xc5\xbc').decode('utf8'))
+
+ def test_utf8_encoded_ldb_Dn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ dn_encoded_utf8 = b'a=' + b'\xc4\x85\xc4\x87\xc4\x99\xc5\x82\xc5\x84\xc3\xb3\xc5\x9b\xc5\xba\xc5\xbc'
+ try:
+ dn = ldb.Dn(l, dn_encoded_utf8)
+ except UnicodeDecodeError as e:
+ raise
+ except TypeError as te:
+ p3errors = ["argument 2 must be str, not bytes",
+ "Can't convert 'bytes' object to str implicitly"]
+ self.assertIn(str(te), p3errors)
+
+ def test_search_attrs(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(len(l.search(ldb.Dn(l, ""), ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0)
+
+ def test_search_string_dn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(len(l.search("", ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0)
+
+ def test_search_attr_string(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertRaises(TypeError, l.search, attrs="dc")
+ self.assertRaises(TypeError, l.search, attrs=b"dc")
+
+ def test_opaque(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ l.set_opaque("my_opaque", l)
+ self.assertTrue(l.get_opaque("my_opaque") is not None)
+ self.assertEqual(None, l.get_opaque("unknown"))
+
+ def test_search_scope_base_empty_db(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(len(l.search(ldb.Dn(l, "dc=foo1"),
+ ldb.SCOPE_BASE)), 0)
+
+ def test_search_scope_onelevel_empty_db(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(len(l.search(ldb.Dn(l, "dc=foo1"),
+ ldb.SCOPE_ONELEVEL)), 0)
+
+ def test_delete(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertRaises(ldb.LdbError, lambda: l.delete(ldb.Dn(l, "dc=foo2")))
+
+ def test_delete_w_unhandled_ctrl(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo1")
+ m["b"] = [b"a"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ self.assertRaises(ldb.LdbError, lambda: l.delete(m.dn, ["search_options:1:2"]))
+ l.delete(m.dn)
+
+ def test_contains(self):
+ name = self.url()
+ l = ldb.Ldb(name, flags=self.flags())
+ self.assertFalse(ldb.Dn(l, "dc=foo3") in l)
+ l = ldb.Ldb(name, flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo3")
+ m["b"] = ["a"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ self.assertTrue(ldb.Dn(l, "dc=foo3") in l)
+ self.assertFalse(ldb.Dn(l, "dc=foo4") in l)
+ finally:
+ l.delete(m.dn)
+
+ def test_get_config_basedn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(None, l.get_config_basedn())
+
+ def test_get_root_basedn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(None, l.get_root_basedn())
+
+ def test_get_schema_basedn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(None, l.get_schema_basedn())
+
+ def test_get_default_basedn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(None, l.get_default_basedn())
+
+ def test_add(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo4")
+ m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo4"))
+
+ def test_search_iterator(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ s = l.search_iterator()
+ s.abandon()
+ try:
+ for me in s:
+ self.fail()
+ self.fail()
+ except RuntimeError as re:
+ pass
+ try:
+ s.abandon()
+ self.fail()
+ except RuntimeError as re:
+ pass
+ try:
+ s.result()
+ self.fail()
+ except RuntimeError as re:
+ pass
+
+ s = l.search_iterator()
+ count = 0
+ for me in s:
+ self.assertTrue(isinstance(me, ldb.Message))
+ count += 1
+ r = s.result()
+ self.assertEqual(len(r), 0)
+ self.assertEqual(count, 0)
+
+ m1 = ldb.Message()
+ m1.dn = ldb.Dn(l, "dc=foo4")
+ m1["bla"] = b"bla"
+ m1["objectUUID"] = b"0123456789abcdef"
+ l.add(m1)
+ try:
+ s = l.search_iterator()
+ msgs = []
+ for me in s:
+ self.assertTrue(isinstance(me, ldb.Message))
+ count += 1
+ msgs.append(me)
+ r = s.result()
+ self.assertEqual(len(r), 0)
+ self.assertEqual(len(msgs), 1)
+ self.assertEqual(msgs[0].dn, m1.dn)
+
+ m2 = ldb.Message()
+ m2.dn = ldb.Dn(l, "dc=foo5")
+ m2["bla"] = b"bla"
+ m2["objectUUID"] = b"0123456789abcdee"
+ l.add(m2)
+
+ s = l.search_iterator()
+ msgs = []
+ for me in s:
+ self.assertTrue(isinstance(me, ldb.Message))
+ count += 1
+ msgs.append(me)
+ r = s.result()
+ self.assertEqual(len(r), 0)
+ self.assertEqual(len(msgs), 2)
+ if msgs[0].dn == m1.dn:
+ self.assertEqual(msgs[0].dn, m1.dn)
+ self.assertEqual(msgs[1].dn, m2.dn)
+ else:
+ self.assertEqual(msgs[0].dn, m2.dn)
+ self.assertEqual(msgs[1].dn, m1.dn)
+
+ s = l.search_iterator()
+ msgs = []
+ for me in s:
+ self.assertTrue(isinstance(me, ldb.Message))
+ count += 1
+ msgs.append(me)
+ break
+ try:
+ s.result()
+ self.fail()
+ except RuntimeError as re:
+ pass
+ for me in s:
+ self.assertTrue(isinstance(me, ldb.Message))
+ count += 1
+ msgs.append(me)
+ break
+ for me in s:
+ self.fail()
+
+ r = s.result()
+ self.assertEqual(len(r), 0)
+ self.assertEqual(len(msgs), 2)
+ if msgs[0].dn == m1.dn:
+ self.assertEqual(msgs[0].dn, m1.dn)
+ self.assertEqual(msgs[1].dn, m2.dn)
+ else:
+ self.assertEqual(msgs[0].dn, m2.dn)
+ self.assertEqual(msgs[1].dn, m1.dn)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo4"))
+ l.delete(ldb.Dn(l, "dc=foo5"))
+
+ def test_add_text(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo4")
+ m["bla"] = "bla"
+ m["objectUUID"] = b"0123456789abcdef"
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo4"))
+
+ def test_add_w_unhandled_ctrl(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo4")
+ m["bla"] = b"bla"
+ self.assertEqual(len(l.search()), 0)
+ self.assertRaises(ldb.LdbError, lambda: l.add(m, ["search_options:1:2"]))
+
+ def test_add_dict(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = {"dn": ldb.Dn(l, "dc=foo5"),
+ "bla": b"bla",
+ "objectUUID": b"0123456789abcdef"}
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo5"))
+
+ def test_add_dict_text(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = {"dn": ldb.Dn(l, "dc=foo5"),
+ "bla": "bla",
+ "objectUUID": b"0123456789abcdef"}
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo5"))
+
+ def test_add_dict_string_dn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = {"dn": "dc=foo6", "bla": b"bla",
+ "objectUUID": b"0123456789abcdef"}
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo6"))
+
+ def test_add_dict_bytes_dn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = {"dn": b"dc=foo6", "bla": b"bla",
+ "objectUUID": b"0123456789abcdef"}
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=foo6"))
+
+ def test_rename(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo7")
+ m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ try:
+ l.rename(ldb.Dn(l, "dc=foo7"), ldb.Dn(l, "dc=bar"))
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=bar"))
+
+ def test_rename_string_dns(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo8")
+ m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ self.assertEqual(len(l.search()), 1)
+ try:
+ l.rename("dc=foo8", "dc=bar")
+ self.assertEqual(len(l.search()), 1)
+ finally:
+ l.delete(ldb.Dn(l, "dc=bar"))
+
+ def test_rename_bad_string_dns(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=foo8")
+ m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
+ self.assertEqual(len(l.search()), 0)
+ l.add(m)
+ self.assertEqual(len(l.search()), 1)
+ self.assertRaises(ldb.LdbError,lambda: l.rename("dcXfoo8", "dc=bar"))
+ self.assertRaises(ldb.LdbError,lambda: l.rename("dc=foo8", "dcXbar"))
+ l.delete(ldb.Dn(l, "dc=foo8"))
+
+ def test_empty_dn(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertEqual(0, len(l.search()))
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=empty")
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ rm = l.search()
+ self.assertEqual(1, len(rm))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
+
+ rm = l.search(m.dn)
+ self.assertEqual(1, len(rm))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
+ rm = l.search(m.dn, attrs=["blah"])
+ self.assertEqual(1, len(rm))
+ self.assertEqual(0, len(rm[0]))
+
+ def test_modify_delete(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modifydelete")
+ m["bla"] = [b"1234"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual([b"1234"], list(rm["bla"]))
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modifydelete")
+ m["bla"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "bla")
+ self.assertEqual(ldb.FLAG_MOD_DELETE, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)
+ self.assertEqual(1, len(rm))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
+ rm = l.search(m.dn, attrs=["bla"])
+ self.assertEqual(1, len(rm))
+ self.assertEqual(0, len(rm[0]))
+ finally:
+ l.delete(ldb.Dn(l, "dc=modifydelete"))
+
+ def test_modify_delete_text(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modifydelete")
+ m.text["bla"] = ["1234"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(["1234"], list(rm.text["bla"]))
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modifydelete")
+ m["bla"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "bla")
+ self.assertEqual(ldb.FLAG_MOD_DELETE, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)
+ self.assertEqual(1, len(rm))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
+ rm = l.search(m.dn, attrs=["bla"])
+ self.assertEqual(1, len(rm))
+ self.assertEqual(0, len(rm[0]))
+ finally:
+ l.delete(ldb.Dn(l, "dc=modifydelete"))
+
+ def test_modify_add(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m["bla"] = [b"1234"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+ self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(3, len(rm))
+ self.assertEqual([b"1234", b"456"], list(rm["bla"]))
+ finally:
+ l.delete(ldb.Dn(l, "dc=add"))
+
+ def test_modify_add_text(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m.text["bla"] = ["1234"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
+ self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(3, len(rm))
+ self.assertEqual(["1234", "456"], list(rm.text["bla"]))
+ finally:
+ l.delete(ldb.Dn(l, "dc=add"))
+
+ def test_modify_replace(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modify2")
+ m["bla"] = [b"1234", b"456"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modify2")
+ m["bla"] = ldb.MessageElement([b"789"], ldb.FLAG_MOD_REPLACE, "bla")
+ self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(3, len(rm))
+ self.assertEqual([b"789"], list(rm["bla"]))
+ rm = l.search(m.dn, attrs=["bla"])[0]
+ self.assertEqual(1, len(rm))
+ finally:
+ l.delete(ldb.Dn(l, "dc=modify2"))
+
+ def test_modify_replace_text(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modify2")
+ m.text["bla"] = ["1234", "456"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=modify2")
+ m["bla"] = ldb.MessageElement(["789"], ldb.FLAG_MOD_REPLACE, "bla")
+ self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(3, len(rm))
+ self.assertEqual(["789"], list(rm.text["bla"]))
+ rm = l.search(m.dn, attrs=["bla"])[0]
+ self.assertEqual(1, len(rm))
+ finally:
+ l.delete(ldb.Dn(l, "dc=modify2"))
+
+ def test_modify_flags_change(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m["bla"] = [b"1234"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+ self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(3, len(rm))
+ self.assertEqual([b"1234", b"456"], list(rm["bla"]))
+
+ # Now create another modify, but switch the flags before we do it
+ m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+ m["bla"].set_flags(ldb.FLAG_MOD_DELETE)
+ l.modify(m)
+ rm = l.search(m.dn, attrs=["bla"])[0]
+ self.assertEqual(1, len(rm))
+ self.assertEqual([b"1234"], list(rm["bla"]))
+ finally:
+ l.delete(ldb.Dn(l, "dc=add"))
+
+ def test_modify_flags_change_text(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m.text["bla"] = ["1234"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(l, "dc=add")
+ m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
+ self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
+ l.modify(m)
+ rm = l.search(m.dn)[0]
+ self.assertEqual(3, len(rm))
+ self.assertEqual(["1234", "456"], list(rm.text["bla"]))
+
+ # Now create another modify, but switch the flags before we do it
+ m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
+ m["bla"].set_flags(ldb.FLAG_MOD_DELETE)
+ l.modify(m)
+ rm = l.search(m.dn, attrs=["bla"])[0]
+ self.assertEqual(1, len(rm))
+ self.assertEqual(["1234"], list(rm.text["bla"]))
+ finally:
+ l.delete(ldb.Dn(l, "dc=add"))
+
+ def test_transaction_commit(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ l.transaction_start()
+ m = ldb.Message(ldb.Dn(l, "dc=foo9"))
+ m["foo"] = [b"bar"]
+ m["objectUUID"] = b"0123456789abcdef"
+ l.add(m)
+ l.transaction_commit()
+ l.delete(m.dn)
+
+ def test_transaction_cancel(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ l.transaction_start()
+ m = ldb.Message(ldb.Dn(l, "dc=foo10"))
+ m["foo"] = [b"bar"]
+ m["objectUUID"] = b"0123456789abcdee"
+ l.add(m)
+ l.transaction_cancel()
+ self.assertEqual(0, len(l.search(ldb.Dn(l, "dc=foo10"))))
+
+ def test_set_debug(self):
+ def my_report_fn(level, text):
+ pass
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ l.set_debug(my_report_fn)
+
+ def test_zero_byte_string(self):
+ """Testing we do not get trapped in the \0 byte in a property string."""
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ l.add({
+ "dn": b"dc=somedn",
+ "objectclass": b"user",
+ "cN": b"LDAPtestUSER",
+ "givenname": b"ldap",
+ "displayname": b"foo\0bar",
+ "objectUUID": b"0123456789abcdef"
+ })
+ res = l.search(expression="(dn=dc=somedn)")
+ self.assertEqual(b"foo\0bar", res[0]["displayname"][0])
+
+ def test_no_crash_broken_expr(self):
+ l = ldb.Ldb(self.url(), flags=self.flags())
+ self.assertRaises(ldb.LdbError, lambda: l.search("", ldb.SCOPE_SUBTREE, "&(dc=*)(dn=*)", ["dc"]))
+
+# Run the SimpleLdb tests against an lmdb backend
+
+
+class SimpleLdbLmdb(SimpleLdb):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(SimpleLdbLmdb, self).setUp()
+
+ def tearDown(self):
+ super(SimpleLdbLmdb, self).tearDown()
+
+
+class SimpleLdbNoLmdb(LdbBaseTest):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') != '0':
+ self.skipTest("lmdb backend enabled")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(SimpleLdbNoLmdb, self).setUp()
+
+ def tearDown(self):
+ super(SimpleLdbNoLmdb, self).tearDown()
+
+ def test_lmdb_disabled(self):
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ try:
+ self.ldb = ldb.Ldb(self.url(), flags=self.flags())
+ self.fail("Should have failed on missing LMDB")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_OTHER)
+
+
+class SearchTests(LdbBaseTest):
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(SearchTests, self).tearDown()
+
+ # Ensure the LDB is closed now, so we close the FD
+ del(self.l)
+
+ def setUp(self):
+ super(SearchTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "search_test.ldb")
+ options = ["modules:rdn_name"]
+ if hasattr(self, 'IDXCHECK'):
+ options.append("disable_full_db_scan_for_self_test:1")
+ self.l = ldb.Ldb(self.url(),
+ flags=self.flags(),
+ options=options)
+ try:
+ self.l.add(self.index)
+ except AttributeError:
+ pass
+
+ self.l.add({"dn": "@ATTRIBUTES",
+ "DC": "CASE_INSENSITIVE"})
+
+ # Note that we can't use the name objectGUID here, as we
+ # want to stay clear of the objectGUID handler in LDB and
+ # instead use just the 16 bytes raw, which we just keep
+ # to printable chars here for ease of handling.
+
+ self.l.add({"dn": "DC=ORG",
+ "name": b"org",
+ "objectUUID": b"0000000000abcdef"})
+ self.l.add({"dn": "DC=EXAMPLE,DC=ORG",
+ "name": b"org",
+ "objectUUID": b"0000000001abcdef"})
+ self.l.add({"dn": "OU=OU1,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #1",
+ "x": "y", "y": "a",
+ "objectUUID": b"0023456789abcde3"})
+ self.l.add({"dn": "OU=OU2,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #2",
+ "x": "y", "y": "a",
+ "objectUUID": b"0023456789abcde4"})
+ self.l.add({"dn": "OU=OU3,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #3",
+ "x": "y", "y": "a",
+ "objectUUID": b"0023456789abcde5"})
+ self.l.add({"dn": "OU=OU4,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #4",
+ "x": "z", "y": "b",
+ "objectUUID": b"0023456789abcde6"})
+ self.l.add({"dn": "OU=OU5,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #5",
+ "x": "y", "y": "a",
+ "objectUUID": b"0023456789abcde7"})
+ self.l.add({"dn": "OU=OU6,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #6",
+ "x": "y", "y": "a",
+ "objectUUID": b"0023456789abcde8"})
+ self.l.add({"dn": "OU=OU7,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #7",
+ "x": "y", "y": "c",
+ "objectUUID": b"0023456789abcde9"})
+ self.l.add({"dn": "OU=OU8,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #8",
+ "x": "y", "y": "b",
+ "objectUUID": b"0023456789abcde0"})
+ self.l.add({"dn": "OU=OU9,DC=EXAMPLE,DC=ORG",
+ "name": b"OU #9",
+ "x": "y", "y": "a",
+ "objectUUID": b"0023456789abcdea"})
+
+ self.l.add({"dn": "DC=EXAMPLE,DC=COM",
+ "name": b"org",
+ "objectUUID": b"0000000011abcdef"})
+
+ self.l.add({"dn": "DC=EXAMPLE,DC=NET",
+ "name": b"org",
+ "objectUUID": b"0000000021abcdef"})
+
+ self.l.add({"dn": "OU=UNIQUE,DC=EXAMPLE,DC=NET",
+ "objectUUID": b"0000000022abcdef"})
+
+ self.l.add({"dn": "DC=SAMBA,DC=ORG",
+ "name": b"samba.org",
+ "objectUUID": b"0123456789abcdef"})
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG",
+ "name": b"Users",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+ self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG",
+ "name": b"OU #1",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde3"})
+ self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG",
+ "name": b"OU #2",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde4"})
+ self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG",
+ "name": b"OU #3",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde5"})
+ self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG",
+ "name": b"OU #4",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde6"})
+ self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG",
+ "name": b"OU #5",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde7"})
+ self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG",
+ "name": b"OU #6",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde8"})
+ self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG",
+ "name": b"OU #7",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde9"})
+ self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG",
+ "name": b"OU #8",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcde0"})
+ self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG",
+ "name": b"OU #9",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcdea"})
+ self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcdeb"})
+ self.l.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "y", "y": "a",
+ "objectUUID": b"0123456789abcdec"})
+ self.l.add({"dn": "OU=OU12,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "y", "y": "b",
+ "objectUUID": b"0123456789abcded"})
+ self.l.add({"dn": "OU=OU13,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcdee"})
+ self.l.add({"dn": "OU=OU14,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd01"})
+ self.l.add({"dn": "OU=OU15,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd02"})
+ self.l.add({"dn": "OU=OU16,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd03"})
+ self.l.add({"dn": "OU=OU17,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd04"})
+ self.l.add({"dn": "OU=OU18,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd05"})
+ self.l.add({"dn": "OU=OU19,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd06"})
+ self.l.add({"dn": "OU=OU20,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "b",
+ "objectUUID": b"0123456789abcd07"})
+ self.l.add({"dn": "OU=OU21,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "c",
+ "objectUUID": b"0123456789abcd08"})
+ self.l.add({"dn": "OU=OU22,DC=SAMBA,DC=ORG",
+ "name": b"OU #10",
+ "x": "x", "y": "c",
+ "objectUUID": b"0123456789abcd09"})
+
+ def test_base(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 1)
+
+ def test_base_lower(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=samba,DC=org",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 1)
+
+ def test_base_or(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(|(ou=ou11)(ou=ou12))")
+ self.assertEqual(len(res11), 1)
+
+ def test_base_or2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(|(x=y)(y=b))")
+ self.assertEqual(len(res11), 1)
+
+ def test_base_and(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(&(ou=ou11)(ou=ou12))")
+ self.assertEqual(len(res11), 0)
+
+ def test_base_and2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(&(x=y)(y=a))")
+ self.assertEqual(len(res11), 1)
+
+ def test_base_false(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(|(ou=ou13)(ou=ou12))")
+ self.assertEqual(len(res11), 0)
+
+ def test_check_base_false(self):
+ """Testing a search"""
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(|(ou=ou13)(ou=ou12))")
+ self.assertEqual(len(res11), 0)
+
+ def test_check_base_error(self):
+ """Testing a search"""
+ checkbaseonsearch = {"dn": "@OPTIONS",
+ "checkBaseOnSearch": b"TRUE"}
+ try:
+ self.l.add(checkbaseonsearch)
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+ m = ldb.Message.from_dict(self.l,
+ checkbaseonsearch)
+ self.l.modify(m)
+
+ try:
+ res11 = self.l.search(base="OU=OU11x,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(|(ou=ou13)(ou=ou12))")
+ self.fail("Should have failed on missing base")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT)
+
+ def test_subtree(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE)
+ if hasattr(self, 'IDXCHECK'):
+ self.fail()
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+ else:
+ self.assertEqual(len(res11), 25)
+
+ def test_subtree2(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_SUBTREE)
+ if hasattr(self, 'IDXCHECK'):
+ self.fail()
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+ else:
+ self.assertEqual(len(res11), 36)
+
+ def test_subtree_and(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(ou=ou11)(ou=ou12))")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_and2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(x=y)(|(y=b)(y=c)))")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_and2_lower(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(x=y)(|(y=b)(y=c)))")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_or(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(|(ou=ou11)(ou=ou12))")
+ self.assertEqual(len(res11), 2)
+
+ def test_subtree_or2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(|(x=y)(y=b))")
+ self.assertEqual(len(res11), 20)
+
+ def test_subtree_or3(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(|(x=y)(y=b)(y=c))")
+ self.assertEqual(len(res11), 22)
+
+ def test_one_and(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ou11)(ou=ou12))")
+ self.assertEqual(len(res11), 0)
+
+ def test_one_and2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(x=y)(y=b))")
+ self.assertEqual(len(res11), 1)
+
+ def test_one_or(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(ou=ou11)(ou=ou12))")
+ self.assertEqual(len(res11), 2)
+
+ def test_one_or2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(x=y)(y=b))")
+ self.assertEqual(len(res11), 20)
+
+ def test_one_or2_lower(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(x=y)(y=b))")
+ self.assertEqual(len(res11), 20)
+
+ def test_one_unindexable(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(y=b*)")
+ if hasattr(self, 'IDX') and \
+ not hasattr(self, 'IDXONE') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 9)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+ def test_one_unindexable_presence(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(y=*)")
+ if hasattr(self, 'IDX') and \
+ not hasattr(self, 'IDXONE') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 24)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+ def test_subtree_and_or(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(|(x=z)(y=b))(x=x)(y=c))")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_and_or2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(x=x)(y=c)(|(x=z)(y=b)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_and_or3(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(|(ou=ou11)(ou=ou10))(|(x=y)(y=b)(y=c)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_subtree_and_or4(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou11)(ou=ou10)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_subtree_and_or5(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(|(x=y)(y=b)(y=c))(ou=ou11))")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_or_and(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(|(x=x)(y=c)(&(x=z)(y=b)))")
+ self.assertEqual(len(res11), 10)
+
+ def test_subtree_large_and_unique(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(ou=ou10)(y=a))")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_unique(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_unique_elsewhere(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unique_elsewhere2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=NET",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_unique_elsewhere3(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unique_elsewhere4(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unique_elsewhere5(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=COM",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unique_elsewhere6(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unique_elsewhere7(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=COM",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unique_here(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=UNIQUE,DC=EXAMPLE,DC=NET",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 1)
+
+ def test_subtree_and_none(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(&(ou=ouX)(y=a))")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_and_idx_record(self):
+ """Testing a search against the index record"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(@IDXDN=DC=SAMBA,DC=ORG)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_and_idxone_record(self):
+ """Testing a search against the index record"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(@IDXONE=DC=SAMBA,DC=ORG)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL)
+ if hasattr(self, 'IDXCHECK') \
+ and not hasattr(self, 'IDXONE'):
+ self.fail()
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+ else:
+ self.assertEqual(len(res11), 24)
+
+ def test_onelevel2(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL)
+ if hasattr(self, 'IDXCHECK') \
+ and not hasattr(self, 'IDXONE'):
+ self.fail()
+ self.fail()
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+ else:
+ self.assertEqual(len(res11), 9)
+
+ def test_onelevel_and_or(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=z)(y=b))(x=x)(y=c))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_and_or2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(x=x)(y=c)(|(x=z)(y=b)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_and_or3(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(ou=ou11)(ou=ou10))(|(x=y)(y=b)(y=c)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_onelevel_and_or4(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou11)(ou=ou10)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_onelevel_and_or5(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=y)(y=b)(y=c))(ou=ou11))")
+ self.assertEqual(len(res11), 1)
+
+ def test_onelevel_or_and(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(x=x)(y=c)(&(x=z)(y=b)))")
+ self.assertEqual(len(res11), 10)
+
+ def test_onelevel_large_and_unique(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ou10)(y=a))")
+ self.assertEqual(len(res11), 1)
+
+ def test_onelevel_unique(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 1)
+
+ def test_onelevel_unique_elsewhere(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_unique_elsewhere2(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=NET",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 1)
+
+ def test_onelevel_unique_elsewhere3(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_unique_elsewhere4(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_unique_elsewhere5(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=COM",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_unique_elsewhere6(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=COM",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_unique_here(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="OU=UNIQUE,DC=EXAMPLE,DC=NET",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_and_none(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ouX)(y=a))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_and_idx_record(self):
+ """Testing a search against the index record"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(@IDXDN=DC=SAMBA,DC=ORG)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_and_idxone_record(self):
+ """Testing a search against the index record"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(@IDXONE=DC=SAMBA,DC=ORG)")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unindexable(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(y=b*)")
+ if hasattr(self, 'IDX') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 9)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+ def test_onelevel_only_and_or(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=z)(y=b))(x=x)(y=c))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_and_or2(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(x=x)(y=c)(|(x=z)(y=b)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_and_or3(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(ou=ou11)(ou=ou10))(|(x=y)(y=b)(y=c)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_and_or4(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou11)(ou=ou10)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_and_or5(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=y)(y=b)(y=c))(ou=ou11))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_or_and(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(x=x)(y=c)(&(x=z)(y=b)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_large_and_unique(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ou10)(y=a))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_unique(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_unique2(self):
+ """Testing a search"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=unique)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_only_and_none(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ouX)(y=a))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_small_and_or(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=z)(y=b))(x=x)(y=c))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_small_and_or2(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(x=x)(y=c)(|(x=z)(y=b)))")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_small_and_or3(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(ou=ou1)(ou=ou2))(|(x=y)(y=b)(y=c)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_onelevel_small_and_or4(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=y)(y=b)(y=c))(|(ou=ou1)(ou=ou2)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_onelevel_small_and_or5(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(|(x=y)(y=b)(y=c))(ou=ou1))")
+ self.assertEqual(len(res11), 1)
+
+ def test_onelevel_small_or_and(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(x=x)(y=c)(&(x=z)(y=b)))")
+ self.assertEqual(len(res11), 2)
+
+ def test_onelevel_small_large_and_unique(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ou9)(y=a))")
+ self.assertEqual(len(res11), 1)
+
+ def test_onelevel_small_unique_elsewhere(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(ou=ou10)")
+ self.assertEqual(len(res11), 0)
+
+ def test_onelevel_small_and_none(self):
+ """Testing a search (showing that onelevel is not subtree)"""
+
+ res11 = self.l.search(base="DC=EXAMPLE,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(&(ou=ouX)(y=a))")
+ self.assertEqual(len(res11), 0)
+
+ def test_subtree_unindexable_presence(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(y=*)")
+ if hasattr(self, 'IDX') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 24)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+ def test_dn_filter_one(self):
+ """Testing that a dn= filter succeeds
+ (or fails with disallowDNFilter
+ set and IDXGUID or (IDX and not IDXONE) mode)
+ when the scope is SCOPE_ONELEVEL.
+
+ This should be made more consistent, but for now lock in
+ the behaviour
+
+ """
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(dn=OU=OU1,DC=SAMBA,DC=ORG)")
+ if hasattr(self, 'disallowDNFilter') and \
+ hasattr(self, 'IDX') and \
+ (hasattr(self, 'IDXGUID') or
+ ((not hasattr(self, 'IDXONE') and hasattr(self, 'IDX')))):
+ self.assertEqual(len(res11), 0)
+ else:
+ self.assertEqual(len(res11), 1)
+
+ def test_dn_filter_subtree(self):
+ """Testing that a dn= filter succeeds
+ (or fails with disallowDNFilter set)
+ when the scope is SCOPE_SUBTREE"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(dn=OU=OU1,DC=SAMBA,DC=ORG)")
+ if hasattr(self, 'disallowDNFilter') \
+ and hasattr(self, 'IDX'):
+ self.assertEqual(len(res11), 0)
+ else:
+ self.assertEqual(len(res11), 1)
+
+ def test_dn_filter_base(self):
+ """Testing that (incorrectly) a dn= filter works
+ when the scope is SCOPE_BASE"""
+
+ res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(dn=OU=OU1,DC=SAMBA,DC=ORG)")
+
+ # At some point we should fix this, but it isn't trivial
+ self.assertEqual(len(res11), 1)
+
+ def test_distinguishedName_filter_one(self):
+ """Testing that a distinguishedName= filter succeeds
+ when the scope is SCOPE_ONELEVEL.
+
+ This should be made more consistent, but for now lock in
+ the behaviour
+
+ """
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(distinguishedName=OU=OU1,DC=SAMBA,DC=ORG)")
+ self.assertEqual(len(res11), 1)
+
+ def test_distinguishedName_filter_subtree(self):
+ """Testing that a distinguishedName= filter succeeds
+ when the scope is SCOPE_SUBTREE"""
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(distinguishedName=OU=OU1,DC=SAMBA,DC=ORG)")
+ self.assertEqual(len(res11), 1)
+
+ def test_distinguishedName_filter_base(self):
+ """Testing that (incorrectly) a distinguishedName= filter works
+ when the scope is SCOPE_BASE"""
+
+ res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(distinguishedName=OU=OU1,DC=SAMBA,DC=ORG)")
+
+ # At some point we should fix this, but it isn't trivial
+ self.assertEqual(len(res11), 1)
+
+ def test_bad_dn_filter_base(self):
+ """Testing that a dn= filter on an invalid DN works
+ when the scope is SCOPE_BASE but
+ returns zero results"""
+
+ res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(dn=OU=OU1,DC=SAMBA,DCXXXX)")
+
+ # At some point we should fix this, but it isn't trivial
+ self.assertEqual(len(res11), 0)
+
+
+ def test_bad_dn_filter_one(self):
+ """Testing that a dn= filter succeeds but returns zero
+ results when the DN is not valid on a SCOPE_ONELEVEL search
+
+ """
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(dn=OU=OU1,DC=SAMBA,DCXXXX)")
+ self.assertEqual(len(res11), 0)
+
+ def test_bad_dn_filter_subtree(self):
+ """Testing that a dn= filter succeeds but returns zero
+ results when the DN is not valid on a SCOPE_SUBTREE search
+
+ """
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(dn=OU=OU1,DC=SAMBA,DCXXXX)")
+ self.assertEqual(len(res11), 0)
+
+ def test_bad_distinguishedName_filter_base(self):
+ """Testing that a distinguishedName= filter on an invalid DN works
+ when the scope is SCOPE_BASE but
+ returns zero results"""
+
+ res11 = self.l.search(base="OU=OU1,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE,
+ expression="(distinguishedName=OU=OU1,DC=SAMBA,DCXXXX)")
+
+ # At some point we should fix this, but it isn't trivial
+ self.assertEqual(len(res11), 0)
+
+
+ def test_bad_distinguishedName_filter_one(self):
+ """Testing that a distinguishedName= filter succeeds but returns zero
+ results when the DN is not valid on a SCOPE_ONELEVEL search
+
+ """
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(distinguishedName=OU=OU1,DC=SAMBA,DCXXXX)")
+ self.assertEqual(len(res11), 0)
+
+ def test_bad_distinguishedName_filter_subtree(self):
+ """Testing that a distinguishedName= filter succeeds but returns zero
+ results when the DN is not valid on a SCOPE_SUBTREE search
+
+ """
+
+ res11 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(distinguishedName=OU=OU1,DC=SAMBA,DCXXXX)")
+ self.assertEqual(len(res11), 0)
+
+ def test_bad_dn_search_base(self):
+ """Testing with a bad base DN (SCOPE_BASE)"""
+
+ try:
+ res11 = self.l.search(base="OU=OU1,DC=SAMBA,DCXXX",
+ scope=ldb.SCOPE_BASE)
+ self.fail("Should have failed with ERR_INVALID_DN_SYNTAX")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX)
+
+
+ def test_bad_dn_search_one(self):
+ """Testing with a bad base DN (SCOPE_ONELEVEL)"""
+
+ try:
+ res11 = self.l.search(base="DC=SAMBA,DCXXXX",
+ scope=ldb.SCOPE_ONELEVEL)
+ self.fail("Should have failed with ERR_INVALID_DN_SYNTAX")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX)
+
+ def test_bad_dn_search_subtree(self):
+ """Testing with a bad base DN (SCOPE_SUBTREE)"""
+
+ try:
+ res11 = self.l.search(base="DC=SAMBA,DCXXXX",
+ scope=ldb.SCOPE_SUBTREE)
+ self.fail("Should have failed with ERR_INVALID_DN_SYNTAX")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX)
+
+
+
+# Run the search tests against an lmdb backend
+class SearchTestsLmdb(SearchTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(SearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(SearchTestsLmdb, self).tearDown()
+
+
+class IndexedSearchTests(SearchTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things"""
+
+ def setUp(self):
+ super(IndexedSearchTests, self).setUp()
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"]})
+ self.IDX = True
+
+
+class IndexedCheckSearchTests(IndexedSearchTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things (full scan disabled)"""
+
+ def setUp(self):
+ self.IDXCHECK = True
+ super(IndexedCheckSearchTests, self).setUp()
+
+
+class IndexedSearchDnFilterTests(SearchTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things"""
+
+ def setUp(self):
+ super(IndexedSearchDnFilterTests, self).setUp()
+ self.l.add({"dn": "@OPTIONS",
+ "disallowDNFilter": "TRUE"})
+ self.disallowDNFilter = True
+
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"]})
+ self.IDX = True
+
+
+class IndexedAndOneLevelSearchTests(SearchTests):
+ """Test searches using the index including @IDXONE, to ensure
+ the index doesn't break things"""
+
+ def setUp(self):
+ super(IndexedAndOneLevelSearchTests, self).setUp()
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXONE": [b"1"]})
+ self.IDX = True
+ self.IDXONE = True
+
+
+class IndexedCheckedAndOneLevelSearchTests(IndexedAndOneLevelSearchTests):
+ """Test searches using the index including @IDXONE, to ensure
+ the index doesn't break things (full scan disabled)"""
+
+ def setUp(self):
+ self.IDXCHECK = True
+ super(IndexedCheckedAndOneLevelSearchTests, self).setUp()
+
+
+class IndexedAndOneLevelDNFilterSearchTests(SearchTests):
+ """Test searches using the index including @IDXONE, to ensure
+ the index doesn't break things"""
+
+ def setUp(self):
+ super(IndexedAndOneLevelDNFilterSearchTests, self).setUp()
+ self.l.add({"dn": "@OPTIONS",
+ "disallowDNFilter": "TRUE",
+ "checkBaseOnSearch": "TRUE"})
+ self.disallowDNFilter = True
+ self.checkBaseOnSearch = True
+
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXONE": [b"1"]})
+ self.IDX = True
+ self.IDXONE = True
+
+
+class GUIDIndexedSearchTests(SearchTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things"""
+
+ def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
+ super(GUIDIndexedSearchTests, self).setUp()
+
+ self.IDXGUID = True
+
+
+class GUIDIndexedDNFilterSearchTests(SearchTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things"""
+
+ def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
+ super(GUIDIndexedDNFilterSearchTests, self).setUp()
+ self.l.add({"dn": "@OPTIONS",
+ "disallowDNFilter": "TRUE",
+ "checkBaseOnSearch": "TRUE"})
+ self.disallowDNFilter = True
+ self.checkBaseOnSearch = True
+ self.IDX = True
+ self.IDXGUID = True
+
+
+class GUIDAndOneLevelIndexedSearchTests(SearchTests):
+ """Test searches using the index including @IDXONE, to ensure
+ the index doesn't break things"""
+
+ def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
+ super(GUIDAndOneLevelIndexedSearchTests, self).setUp()
+ self.l.add({"dn": "@OPTIONS",
+ "disallowDNFilter": "TRUE",
+ "checkBaseOnSearch": "TRUE"})
+ self.disallowDNFilter = True
+ self.checkBaseOnSearch = True
+ self.IDX = True
+ self.IDXGUID = True
+ self.IDXONE = True
+
+
+class GUIDIndexedSearchTestsLmdb(GUIDIndexedSearchTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(GUIDIndexedSearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDIndexedSearchTestsLmdb, self).tearDown()
+
+
+class GUIDIndexedDNFilterSearchTestsLmdb(GUIDIndexedDNFilterSearchTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(GUIDIndexedDNFilterSearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDIndexedDNFilterSearchTestsLmdb, self).tearDown()
+
+
+class GUIDAndOneLevelIndexedSearchTestsLmdb(GUIDAndOneLevelIndexedSearchTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(GUIDAndOneLevelIndexedSearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDAndOneLevelIndexedSearchTestsLmdb, self).tearDown()
+
+
+class AddModifyTests(LdbBaseTest):
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(AddModifyTests, self).tearDown()
+
+ # Ensure the LDB is closed now, so we close the FD
+ del(self.l)
+
+ def setUp(self):
+ super(AddModifyTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "add_test.ldb")
+ self.l = ldb.Ldb(self.url(),
+ flags=self.flags(),
+ options=["modules:rdn_name"])
+ try:
+ self.l.add(self.index)
+ except AttributeError:
+ pass
+
+ self.l.add({"dn": "DC=SAMBA,DC=ORG",
+ "name": b"samba.org",
+ "objectUUID": b"0123456789abcdef"})
+ self.l.add({"dn": "@ATTRIBUTES",
+ "objectUUID": "UNIQUE_INDEX"})
+
+ def test_add_dup(self):
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ try:
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+ self.fail("Should have failed adding duplicate entry")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ def test_add_bad(self):
+ try:
+ self.l.add({"dn": "BAD,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.fail("Should have failed adding entry with invalid DN")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX)
+
+ def test_add_del_add(self):
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.delete("OU=DUP,DC=SAMBA,DC=ORG")
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ def test_add_move_add(self):
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ def test_add_move_fail_move_move(self):
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ res2 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectUUID=0123456789abcde1)")
+ self.assertEqual(len(res2), 1)
+ self.assertEqual(str(res2[0].dn), "OU=DUP,DC=SAMBA,DC=ORG")
+
+ res3 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectUUID=0123456789abcde2)")
+ self.assertEqual(len(res3), 1)
+ self.assertEqual(str(res3[0].dn), "OU=DUP2,DC=SAMBA,DC=ORG")
+
+ try:
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+ self.fail("Should have failed on duplicate DN")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ self.l.rename("OU=DUP2,DC=SAMBA,DC=ORG",
+ "OU=DUP3,DC=SAMBA,DC=ORG")
+
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+
+ res2 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectUUID=0123456789abcde1)")
+ self.assertEqual(len(res2), 1)
+ self.assertEqual(str(res2[0].dn), "OU=DUP2,DC=SAMBA,DC=ORG")
+
+ res3 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectUUID=0123456789abcde2)")
+ self.assertEqual(len(res3), 1)
+ self.assertEqual(str(res3[0].dn), "OU=DUP3,DC=SAMBA,DC=ORG")
+
+ def test_move_missing(self):
+ try:
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+ self.fail("Should have failed on missing")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT)
+
+ def test_move_missing2(self):
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ try:
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+ self.fail("Should have failed on missing")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT)
+
+ def test_move_bad(self):
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ try:
+ self.l.rename("OUXDUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+ self.fail("Should have failed on invalid DN")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX)
+
+ def test_move_bad2(self):
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ try:
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OUXDUP2,DC=SAMBA,DC=ORG")
+ self.fail("Should have failed on missing")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_INVALID_DN_SYNTAX)
+
+ def test_move_fail_move_add(self):
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+ try:
+ self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
+ "OU=DUP2,DC=SAMBA,DC=ORG")
+ self.fail("Should have failed on duplicate DN")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ self.l.rename("OU=DUP2,DC=SAMBA,DC=ORG",
+ "OU=DUP3,DC=SAMBA,DC=ORG")
+
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde3"})
+
+
+class AddModifyTestsLmdb(AddModifyTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(AddModifyTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(AddModifyTestsLmdb, self).tearDown()
+
+
+class IndexedAddModifyTests(AddModifyTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things"""
+
+ def setUp(self):
+ if not hasattr(self, 'index'):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou", b"objectUUID", b"z"],
+ "@IDXONE": [b"1"]}
+ super(IndexedAddModifyTests, self).setUp()
+
+ def test_duplicate_GUID(self):
+ try:
+ self.l.add({"dn": "OU=DUPGUID,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcdef"})
+ self.fail("Should have failed adding duplicate GUID")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION)
+
+ def test_duplicate_name_dup_GUID(self):
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"a123456789abcdef"})
+ try:
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"a123456789abcdef"})
+ self.fail("Should have failed adding duplicate GUID")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ def test_duplicate_name_dup_GUID2(self):
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"abc3456789abcdef"})
+ try:
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"aaa3456789abcdef"})
+ self.fail("Should have failed adding duplicate DN")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ # Checking the GUID didn't stick in the index
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"aaa3456789abcdef"})
+
+ def test_add_dup_guid_add(self):
+ self.l.add({"dn": "OU=DUP,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ try:
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde1"})
+ self.fail("Should have failed on duplicate GUID")
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION)
+
+ self.l.add({"dn": "OU=DUP2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "x": "z", "y": "a",
+ "objectUUID": b"0123456789abcde2"})
+
+ def test_duplicate_index_values(self):
+ self.l.add({"dn": "OU=DIV1,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "z": "1",
+ "objectUUID": b"0123456789abcdff"})
+ self.l.add({"dn": "OU=DIV2,DC=SAMBA,DC=ORG",
+ "name": b"Admins",
+ "z": "1",
+ "objectUUID": b"0123456789abcdfd"})
+
+
+class GUIDIndexedAddModifyTests(IndexedAddModifyTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things"""
+
+ def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
+ super(GUIDIndexedAddModifyTests, self).setUp()
+
+
+class GUIDTransIndexedAddModifyTests(GUIDIndexedAddModifyTests):
+ """Test GUID index behaviour insdie the transaction"""
+
+ def setUp(self):
+ super(GUIDTransIndexedAddModifyTests, self).setUp()
+ self.l.transaction_start()
+
+ def tearDown(self):
+ self.l.transaction_commit()
+ super(GUIDTransIndexedAddModifyTests, self).tearDown()
+
+
+class TransIndexedAddModifyTests(IndexedAddModifyTests):
+ """Test index behaviour insdie the transaction"""
+
+ def setUp(self):
+ super(TransIndexedAddModifyTests, self).setUp()
+ self.l.transaction_start()
+
+ def tearDown(self):
+ self.l.transaction_commit()
+ super(TransIndexedAddModifyTests, self).tearDown()
+
+
+class GuidIndexedAddModifyTestsLmdb(GUIDIndexedAddModifyTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(GuidIndexedAddModifyTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GuidIndexedAddModifyTestsLmdb, self).tearDown()
+
+
+class GuidTransIndexedAddModifyTestsLmdb(GUIDTransIndexedAddModifyTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(GuidTransIndexedAddModifyTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GuidTransIndexedAddModifyTestsLmdb, self).tearDown()
+
+
+class BadIndexTests(LdbBaseTest):
+ def setUp(self):
+ super(BadIndexTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ self.ldb = ldb.Ldb(self.url(), flags=self.flags())
+ if hasattr(self, 'IDXGUID'):
+ self.ldb.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]})
+ else:
+ self.ldb.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"]})
+
+ super(BadIndexTests, self).setUp()
+
+ def test_unique(self):
+ self.ldb.add({"dn": "x=x,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1",
+ "y": "1"})
+ self.ldb.add({"dn": "x=y,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde2",
+ "y": "1"})
+ self.ldb.add({"dn": "x=z,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde3",
+ "y": "1"})
+
+ res = self.ldb.search(expression="(y=1)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 3)
+
+ # Now set this to unique index, but forget to check the result
+ try:
+ self.ldb.add({"dn": "@ATTRIBUTES",
+ "y": "UNIQUE_INDEX"})
+ self.fail()
+ except ldb.LdbError:
+ pass
+
+ # We must still have a working index
+ res = self.ldb.search(expression="(y=1)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 3)
+
+ def test_unique_transaction(self):
+ self.ldb.add({"dn": "x=x,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1",
+ "y": "1"})
+ self.ldb.add({"dn": "x=y,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde2",
+ "y": "1"})
+ self.ldb.add({"dn": "x=z,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde3",
+ "y": "1"})
+
+ res = self.ldb.search(expression="(y=1)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 3)
+
+ self.ldb.transaction_start()
+
+ # Now set this to unique index, but forget to check the result
+ try:
+ self.ldb.add({"dn": "@ATTRIBUTES",
+ "y": "UNIQUE_INDEX"})
+ except ldb.LdbError:
+ pass
+
+ try:
+ self.ldb.transaction_commit()
+ self.fail()
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_OPERATIONS_ERROR)
+
+ # We must still have a working index
+ res = self.ldb.search(expression="(y=1)",
+ base="dc=samba,dc=org")
+
+ self.assertEqual(len(res), 3)
+
+ def test_casefold(self):
+ self.ldb.add({"dn": "x=x,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1",
+ "y": "a"})
+ self.ldb.add({"dn": "x=y,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde2",
+ "y": "A"})
+ self.ldb.add({"dn": "x=z,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde3",
+ "y": ["a", "A"]})
+
+ res = self.ldb.search(expression="(y=a)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 2)
+
+ self.ldb.add({"dn": "@ATTRIBUTES",
+ "y": "CASE_INSENSITIVE"})
+
+ # We must still have a working index
+ res = self.ldb.search(expression="(y=a)",
+ base="dc=samba,dc=org")
+
+ if hasattr(self, 'IDXGUID'):
+ self.assertEqual(len(res), 3)
+ else:
+ # We should not return this entry twice, but sadly
+ # we have not yet fixed
+ # https://bugzilla.samba.org/show_bug.cgi?id=13361
+ self.assertEqual(len(res), 4)
+
+ def test_casefold_transaction(self):
+ self.ldb.add({"dn": "x=x,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1",
+ "y": "a"})
+ self.ldb.add({"dn": "x=y,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde2",
+ "y": "A"})
+ self.ldb.add({"dn": "x=z,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde3",
+ "y": ["a", "A"]})
+
+ res = self.ldb.search(expression="(y=a)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 2)
+
+ self.ldb.transaction_start()
+
+ self.ldb.add({"dn": "@ATTRIBUTES",
+ "y": "CASE_INSENSITIVE"})
+
+ self.ldb.transaction_commit()
+
+ # We must still have a working index
+ res = self.ldb.search(expression="(y=a)",
+ base="dc=samba,dc=org")
+
+ if hasattr(self, 'IDXGUID'):
+ self.assertEqual(len(res), 3)
+ else:
+ # We should not return this entry twice, but sadly
+ # we have not yet fixed
+ # https://bugzilla.samba.org/show_bug.cgi?id=13361
+ self.assertEqual(len(res), 4)
+
+ def test_modify_transaction(self):
+ self.ldb.add({"dn": "x=y,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1",
+ "y": "2",
+ "z": "2"})
+
+ res = self.ldb.search(expression="(y=2)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+
+ self.ldb.add({"dn": "@ATTRIBUTES",
+ "y": "UNIQUE_INDEX"})
+
+ self.ldb.transaction_start()
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.ldb, "x=y,dc=samba,dc=org")
+ m["0"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "y")
+ m["1"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "not-here")
+
+ try:
+ self.ldb.modify(m)
+ self.fail()
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_NO_SUCH_ATTRIBUTE)
+
+ try:
+ self.ldb.transaction_commit()
+ # We should fail here, but we want to be sure
+ # we fail below
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_OPERATIONS_ERROR)
+
+ # The index should still be pointing to x=y
+ res = self.ldb.search(expression="(y=2)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+
+ try:
+ self.ldb.add({"dn": "x=y2,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde2",
+ "y": "2"})
+ self.fail("Added unique attribute twice")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION)
+
+ res = self.ldb.search(expression="(y=2)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+ self.assertEqual(str(res[0].dn), "x=y,dc=samba,dc=org")
+
+ def tearDown(self):
+ super(BadIndexTests, self).tearDown()
+
+
+class GUIDBadIndexTests(BadIndexTests):
+ """Test Bad index things with GUID index mode"""
+
+ def setUp(self):
+ self.IDXGUID = True
+
+ super(GUIDBadIndexTests, self).setUp()
+
+
+class GUIDBadIndexTestsLmdb(BadIndexTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ self.IDXGUID = True
+ super(GUIDBadIndexTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDBadIndexTestsLmdb, self).tearDown()
+
+
+class BatchModeTests(LdbBaseTest):
+
+ def setUp(self):
+ super(BatchModeTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ self.ldb = ldb.Ldb(self.url(),
+ flags=self.flags(),
+ options=["batch_mode:1"])
+ if hasattr(self, 'IDXGUID'):
+ self.ldb.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]})
+ else:
+ self.ldb.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"]})
+
+ def test_modify_transaction(self):
+ self.ldb.add({"dn": "x=y,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1",
+ "y": "2",
+ "z": "2"})
+
+ res = self.ldb.search(expression="(y=2)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+
+ self.ldb.add({"dn": "@ATTRIBUTES",
+ "y": "UNIQUE_INDEX"})
+
+ self.ldb.transaction_start()
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.ldb, "x=y,dc=samba,dc=org")
+ m["0"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "y")
+ m["1"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "not-here")
+
+ try:
+ self.ldb.modify(m)
+ self.fail()
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_NO_SUCH_ATTRIBUTE)
+
+ try:
+ self.ldb.transaction_commit()
+ self.fail("Commit should have failed as we were in batch mode")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_OPERATIONS_ERROR)
+
+ def tearDown(self):
+ super(BatchModeTests, self).tearDown()
+
+
+class DnTests(TestCase):
+
+ def setUp(self):
+ super(DnTests, self).setUp()
+ self.ldb = ldb.Ldb()
+
+ def tearDown(self):
+ super(DnTests, self).tearDown()
+ del(self.ldb)
+
+ def test_set_dn_invalid(self):
+ x = ldb.Message()
+
+ def assign():
+ x.dn = "astring"
+ self.assertRaises(TypeError, assign)
+
+ def test_eq(self):
+ x = ldb.Dn(self.ldb, "dc=foo11,bar=bloe")
+ y = ldb.Dn(self.ldb, "dc=foo11,bar=bloe")
+ self.assertEqual(x, y)
+ y = ldb.Dn(self.ldb, "dc=foo11,bar=blie")
+ self.assertNotEqual(x, y)
+
+ def test_str(self):
+ x = ldb.Dn(self.ldb, "dc=foo12,bar=bloe")
+ self.assertEqual(x.__str__(), "dc=foo12,bar=bloe")
+
+ def test_repr(self):
+ x = ldb.Dn(self.ldb, "dc=foo13,bla=blie")
+ self.assertEqual(x.__repr__(), "Dn('dc=foo13,bla=blie')")
+
+ def test_get_casefold_2(self):
+ x = ldb.Dn(self.ldb, "dc=foo14,bar=bloe")
+ self.assertEqual(x.get_casefold(), "DC=FOO14,BAR=bloe")
+
+ def test_validate(self):
+ x = ldb.Dn(self.ldb, "dc=foo15,bar=bloe")
+ self.assertTrue(x.validate())
+
+ def test_parent(self):
+ x = ldb.Dn(self.ldb, "dc=foo16,bar=bloe")
+ self.assertEqual("bar=bloe", x.parent().__str__())
+
+ def test_parent_nonexistent(self):
+ x = ldb.Dn(self.ldb, "@BLA")
+ self.assertEqual(None, x.parent())
+
+ def test_is_valid(self):
+ x = ldb.Dn(self.ldb, "dc=foo18,dc=bloe")
+ self.assertTrue(x.is_valid())
+ x = ldb.Dn(self.ldb, "")
+ self.assertTrue(x.is_valid())
+
+ def test_is_special(self):
+ x = ldb.Dn(self.ldb, "dc=foo19,bar=bloe")
+ self.assertFalse(x.is_special())
+ x = ldb.Dn(self.ldb, "@FOOBAR")
+ self.assertTrue(x.is_special())
+
+ def test_check_special(self):
+ x = ldb.Dn(self.ldb, "dc=foo20,bar=bloe")
+ self.assertFalse(x.check_special("FOOBAR"))
+ x = ldb.Dn(self.ldb, "@FOOBAR")
+ self.assertTrue(x.check_special("@FOOBAR"))
+
+ def test_len(self):
+ x = ldb.Dn(self.ldb, "dc=foo21,bar=bloe")
+ self.assertEqual(2, len(x))
+ x = ldb.Dn(self.ldb, "dc=foo21")
+ self.assertEqual(1, len(x))
+
+ def test_add_child(self):
+ x = ldb.Dn(self.ldb, "dc=foo22,bar=bloe")
+ self.assertTrue(x.add_child(ldb.Dn(self.ldb, "bla=bloe")))
+ self.assertEqual("bla=bloe,dc=foo22,bar=bloe", x.__str__())
+
+ def test_add_base(self):
+ x = ldb.Dn(self.ldb, "dc=foo23,bar=bloe")
+ base = ldb.Dn(self.ldb, "bla=bloe")
+ self.assertTrue(x.add_base(base))
+ self.assertEqual("dc=foo23,bar=bloe,bla=bloe", x.__str__())
+
+ def test_add_child_str(self):
+ x = ldb.Dn(self.ldb, "dc=foo22,bar=bloe")
+ self.assertTrue(x.add_child("bla=bloe"))
+ self.assertEqual("bla=bloe,dc=foo22,bar=bloe", x.__str__())
+
+ def test_add_base_str(self):
+ x = ldb.Dn(self.ldb, "dc=foo23,bar=bloe")
+ base = "bla=bloe"
+ self.assertTrue(x.add_base(base))
+ self.assertEqual("dc=foo23,bar=bloe,bla=bloe", x.__str__())
+
+ def test_add(self):
+ x = ldb.Dn(self.ldb, "dc=foo24")
+ y = ldb.Dn(self.ldb, "bar=bla")
+ self.assertEqual("dc=foo24,bar=bla", str(x + y))
+
+ def test_remove_base_components(self):
+ x = ldb.Dn(self.ldb, "dc=foo24,dc=samba,dc=org")
+ x.remove_base_components(len(x) - 1)
+ self.assertEqual("dc=foo24", str(x))
+
+ def test_parse_ldif(self):
+ msgs = self.ldb.parse_ldif("dn: foo=bar\n")
+ msg = next(msgs)
+ self.assertEqual("foo=bar", str(msg[1].dn))
+ self.assertTrue(isinstance(msg[1], ldb.Message))
+ ldif = self.ldb.write_ldif(msg[1], ldb.CHANGETYPE_NONE)
+ self.assertEqual("dn: foo=bar\n\n", ldif)
+
+ def test_parse_ldif_more(self):
+ msgs = self.ldb.parse_ldif("dn: foo=bar\n\n\ndn: bar=bar")
+ msg = next(msgs)
+ self.assertEqual("foo=bar", str(msg[1].dn))
+ msg = next(msgs)
+ self.assertEqual("bar=bar", str(msg[1].dn))
+
+ def test_print_ldif(self):
+ ldif = '''dn: dc=foo27
+foo: foo
+
+'''
+ self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27"))
+ self.msg["foo"] = [b"foo"]
+ self.assertEqual(ldif,
+ self.ldb.write_ldif(self.msg,
+ ldb.CHANGETYPE_NONE))
+
+ def test_print_ldif_binary(self):
+ # this also confirms that ldb flags are set even without a URL)
+ self.ldb = ldb.Ldb(flags=ldb.FLG_SHOW_BINARY)
+ ldif = '''dn: dc=foo27
+foo: f
+öö
+
+'''
+ self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27"))
+ self.msg["foo"] = ["f\nöö"]
+ self.assertEqual(ldif,
+ self.ldb.write_ldif(self.msg,
+ ldb.CHANGETYPE_NONE))
+
+
+ def test_print_ldif_no_base64_bad(self):
+ ldif = '''dn: dc=foo27
+foo: f
+öö
+
+'''
+ self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27"))
+ self.msg["foo"] = ["f\nöö"]
+ self.msg["foo"].set_flags(ldb.FLAG_FORCE_NO_BASE64_LDIF)
+ self.assertEqual(ldif,
+ self.ldb.write_ldif(self.msg,
+ ldb.CHANGETYPE_NONE))
+
+ def test_print_ldif_no_base64_good(self):
+ ldif = '''dn: dc=foo27
+foo: föö
+
+'''
+ self.msg = ldb.Message(ldb.Dn(self.ldb, "dc=foo27"))
+ self.msg["foo"] = ["föö"]
+ self.msg["foo"].set_flags(ldb.FLAG_FORCE_NO_BASE64_LDIF)
+ self.assertEqual(ldif,
+ self.ldb.write_ldif(self.msg,
+ ldb.CHANGETYPE_NONE))
+
+ def test_canonical_string(self):
+ x = ldb.Dn(self.ldb, "dc=foo25,bar=bloe")
+ self.assertEqual("/bloe/foo25", x.canonical_str())
+
+ def test_canonical_ex_string(self):
+ x = ldb.Dn(self.ldb, "dc=foo26,bar=bloe")
+ self.assertEqual("/bloe\nfoo26", x.canonical_ex_str())
+
+ def test_ldb_is_child_of(self):
+ """Testing ldb_dn_compare_dn"""
+ dn1 = ldb.Dn(self.ldb, "dc=base")
+ dn2 = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ dn3 = ldb.Dn(self.ldb, "cn=bar,dc=base")
+ dn4 = ldb.Dn(self.ldb, "cn=baz,cn=bar,dc=base")
+
+ self.assertTrue(dn1.is_child_of(dn1))
+ self.assertTrue(dn2.is_child_of(dn1))
+ self.assertTrue(dn4.is_child_of(dn1))
+ self.assertTrue(dn4.is_child_of(dn3))
+ self.assertTrue(dn4.is_child_of(dn4))
+ self.assertFalse(dn3.is_child_of(dn2))
+ self.assertFalse(dn1.is_child_of(dn4))
+
+ def test_ldb_is_child_of_str(self):
+ """Testing ldb_dn_compare_dn"""
+ dn1_str = "dc=base"
+ dn2_str = "cn=foo,dc=base"
+ dn3_str = "cn=bar,dc=base"
+ dn4_str = "cn=baz,cn=bar,dc=base"
+
+ dn1 = ldb.Dn(self.ldb, dn1_str)
+ dn2 = ldb.Dn(self.ldb, dn2_str)
+ dn3 = ldb.Dn(self.ldb, dn3_str)
+ dn4 = ldb.Dn(self.ldb, dn4_str)
+
+ self.assertTrue(dn1.is_child_of(dn1_str))
+ self.assertTrue(dn2.is_child_of(dn1_str))
+ self.assertTrue(dn4.is_child_of(dn1_str))
+ self.assertTrue(dn4.is_child_of(dn3_str))
+ self.assertTrue(dn4.is_child_of(dn4_str))
+ self.assertFalse(dn3.is_child_of(dn2_str))
+ self.assertFalse(dn1.is_child_of(dn4_str))
+
+ def test_get_component_name(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertEqual(dn.get_component_name(0), 'cn')
+ self.assertEqual(dn.get_component_name(1), 'dc')
+ self.assertEqual(dn.get_component_name(2), None)
+ self.assertEqual(dn.get_component_name(-1), None)
+
+ def test_get_component_value(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertEqual(dn.get_component_value(0), 'foo')
+ self.assertEqual(dn.get_component_value(1), 'base')
+ self.assertEqual(dn.get_component_name(2), None)
+ self.assertEqual(dn.get_component_name(-1), None)
+
+ def test_set_component(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ dn.set_component(0, 'cn', 'bar')
+ self.assertEqual(str(dn), "cn=bar,dc=base")
+ dn.set_component(1, 'o', 'asep')
+ self.assertEqual(str(dn), "cn=bar,o=asep")
+ self.assertRaises(TypeError, dn.set_component, 2, 'dc', 'base')
+ self.assertEqual(str(dn), "cn=bar,o=asep")
+ dn.set_component(1, 'o', 'a,b+c')
+ self.assertEqual(str(dn), r"cn=bar,o=a\,b\+c")
+
+ def test_set_component_bytes(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ dn.set_component(0, 'cn', b'bar')
+ self.assertEqual(str(dn), "cn=bar,dc=base")
+ dn.set_component(1, 'o', b'asep')
+ self.assertEqual(str(dn), "cn=bar,o=asep")
+
+ def test_set_component_none(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,cn=bar,dc=base")
+ self.assertRaises(TypeError, dn.set_component, 1, 'cn', None)
+
+ def test_get_extended_component_null(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,cn=bar,dc=base")
+ self.assertEqual(dn.get_extended_component("TEST"), None)
+
+ def test_get_extended_component(self):
+ self.ldb._register_test_extensions()
+ dn = ldb.Dn(self.ldb, "<TEST=foo>;cn=bar,dc=base")
+ self.assertEqual(dn.get_extended_component("TEST"), b"foo")
+
+ def test_set_extended_component(self):
+ self.ldb._register_test_extensions()
+ dn = ldb.Dn(self.ldb, "dc=base")
+ dn.set_extended_component("TEST", "foo")
+ self.assertEqual(dn.get_extended_component("TEST"), b"foo")
+ dn.set_extended_component("TEST", b"bar")
+ self.assertEqual(dn.get_extended_component("TEST"), b"bar")
+
+ def test_extended_str(self):
+ self.ldb._register_test_extensions()
+ dn = ldb.Dn(self.ldb, "<TEST=foo>;cn=bar,dc=base")
+ self.assertEqual(dn.extended_str(), "<TEST=foo>;cn=bar,dc=base")
+
+ def test_get_rdn_name(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertEqual(dn.get_rdn_name(), 'cn')
+
+ def test_get_rdn_value(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertEqual(dn.get_rdn_value(), 'foo')
+
+ def test_get_casefold(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertEqual(dn.get_casefold(), 'CN=FOO,DC=BASE')
+
+ def test_get_linearized(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertEqual(dn.get_linearized(), 'cn=foo,dc=base')
+
+ def test_is_null(self):
+ dn = ldb.Dn(self.ldb, "cn=foo,dc=base")
+ self.assertFalse(dn.is_null())
+
+ dn = ldb.Dn(self.ldb, '')
+ self.assertTrue(dn.is_null())
+
+
+class LdbMsgTests(TestCase):
+
+ def setUp(self):
+ super(LdbMsgTests, self).setUp()
+ self.msg = ldb.Message()
+
+ def test_init_dn(self):
+ self.msg = ldb.Message(ldb.Dn(ldb.Ldb(), "dc=foo27"))
+ self.assertEqual("dc=foo27", str(self.msg.dn))
+
+ def test_iter_items(self):
+ self.assertEqual(0, len(self.msg.items()))
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "dc=foo28")
+ self.assertEqual(1, len(self.msg.items()))
+
+ def test_items(self):
+ self.msg["foo"] = ["foo"]
+ self.msg["bar"] = ["bar"]
+ try:
+ items = self.msg.items()
+ except:
+ self.fail()
+ self.assertEqual([("foo", ldb.MessageElement(["foo"])),
+ ("bar", ldb.MessageElement(["bar"]))],
+ items)
+
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "dc=test")
+ try:
+ items = self.msg.items()
+ except:
+ self.fail()
+ self.assertEqual([("dn", ldb.Dn(ldb.Ldb(), "dc=test")),
+ ("foo", ldb.MessageElement(["foo"])),
+ ("bar", ldb.MessageElement(["bar"]))],
+ items)
+
+ def test_repr(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "dc=foo29")
+ self.msg["dc"] = b"foo"
+ self.assertIn(repr(self.msg), [
+ "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement([b'foo'])})",
+ "Message({'dc': MessageElement([b'foo']), 'dn': Dn('dc=foo29')})",
+ ])
+ self.assertIn(repr(self.msg.text), [
+ "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement([b'foo'])}).text",
+ "Message({'dc': MessageElement([b'foo']), 'dn': Dn('dc=foo29')}).text",
+ ])
+
+ def test_len(self):
+ self.assertEqual(0, len(self.msg))
+
+ def test_notpresent(self):
+ self.assertRaises(KeyError, lambda: self.msg["foo"])
+
+ def test_invalid(self):
+ try:
+ self.assertRaises(TypeError, lambda: self.msg[42])
+ except KeyError:
+ self.fail()
+
+ def test_del(self):
+ del self.msg["foo"]
+
+ def test_add(self):
+ self.msg.add(ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla"))
+
+ def test_add_text(self):
+ self.msg.add(ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla"))
+
+ def test_elements_empty(self):
+ self.assertEqual([], self.msg.elements())
+
+ def test_elements(self):
+ el = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+ self.msg.add(el)
+ self.assertEqual([el], self.msg.elements())
+ self.assertEqual([el.text], self.msg.text.elements())
+
+ def test_add_value(self):
+ self.assertEqual(0, len(self.msg))
+ self.msg["foo"] = [b"foo"]
+ self.assertEqual(1, len(self.msg))
+
+ def test_add_value_text(self):
+ self.assertEqual(0, len(self.msg))
+ self.msg["foo"] = ["foo"]
+ self.assertEqual(1, len(self.msg))
+
+ def test_add_value_multiple(self):
+ self.assertEqual(0, len(self.msg))
+ self.msg["foo"] = [b"foo", b"bla"]
+ self.assertEqual(1, len(self.msg))
+ self.assertEqual([b"foo", b"bla"], list(self.msg["foo"]))
+
+ def test_add_value_multiple_text(self):
+ self.assertEqual(0, len(self.msg))
+ self.msg["foo"] = ["foo", "bla"]
+ self.assertEqual(1, len(self.msg))
+ self.assertEqual(["foo", "bla"], list(self.msg.text["foo"]))
+
+ def test_set_value(self):
+ self.msg["foo"] = [b"fool"]
+ self.assertEqual([b"fool"], list(self.msg["foo"]))
+ self.msg["foo"] = [b"bar"]
+ self.assertEqual([b"bar"], list(self.msg["foo"]))
+
+ def test_set_value_text(self):
+ self.msg["foo"] = ["fool"]
+ self.assertEqual(["fool"], list(self.msg.text["foo"]))
+ self.msg["foo"] = ["bar"]
+ self.assertEqual(["bar"], list(self.msg.text["foo"]))
+
+ def test_keys(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.msg["foo"] = [b"bla"]
+ self.msg["bar"] = [b"bla"]
+ self.assertEqual(["dn", "foo", "bar"], list(self.msg.keys()))
+
+ def test_keys_text(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.msg["foo"] = ["bla"]
+ self.msg["bar"] = ["bla"]
+ self.assertEqual(["dn", "foo", "bar"], list(self.msg.text.keys()))
+
+ def test_dn(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.assertEqual("@BASEINFO", self.msg.dn.__str__())
+
+ def test_get_dn(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.assertEqual("@BASEINFO", self.msg.get("dn").__str__())
+
+ def test_dn_text(self):
+ self.msg.text.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.assertEqual("@BASEINFO", str(self.msg.dn))
+ self.assertEqual("@BASEINFO", str(self.msg.text.dn))
+
+ def test_get_dn_text(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.assertEqual("@BASEINFO", str(self.msg.get("dn")))
+ self.assertEqual("@BASEINFO", str(self.msg.text.get("dn")))
+
+ def test_get_invalid(self):
+ self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
+ self.assertRaises(TypeError, self.msg.get, 42)
+
+ def test_get_other(self):
+ self.msg["foo"] = [b"bar"]
+ self.assertEqual(b"bar", self.msg.get("foo")[0])
+ self.assertEqual(b"bar", self.msg.get("foo", idx=0))
+ self.assertEqual(None, self.msg.get("foo", idx=1))
+ self.assertEqual("", self.msg.get("foo", default='', idx=1))
+
+ def test_get_other_text(self):
+ self.msg["foo"] = ["bar"]
+ self.assertEqual(["bar"], list(self.msg.text.get("foo")))
+ self.assertEqual("bar", self.msg.text.get("foo")[0])
+ self.assertEqual("bar", self.msg.text.get("foo", idx=0))
+ self.assertEqual(None, self.msg.get("foo", idx=1))
+ self.assertEqual("", self.msg.get("foo", default='', idx=1))
+
+ def test_get_default(self):
+ self.assertEqual(None, self.msg.get("tatayoyo", idx=0))
+ self.assertEqual("anniecordie", self.msg.get("tatayoyo", "anniecordie"))
+
+ def test_get_default_text(self):
+ self.assertEqual(None, self.msg.text.get("tatayoyo", idx=0))
+ self.assertEqual("anniecordie", self.msg.text.get("tatayoyo", "anniecordie"))
+
+ def test_get_unknown(self):
+ self.assertEqual(None, self.msg.get("lalalala"))
+
+ def test_get_unknown_text(self):
+ self.assertEqual(None, self.msg.text.get("lalalala"))
+
+ def test_contains(self):
+ self.msg['foo'] = ['bar']
+ self.assertIn('foo', self.msg)
+
+ self.msg['Foo'] = ['bar']
+ self.assertIn('Foo', self.msg)
+
+ def test_contains_case(self):
+ self.msg['foo'] = ['bar']
+ self.assertIn('Foo', self.msg)
+
+ self.msg['Foo'] = ['bar']
+ self.assertIn('foo', self.msg)
+
+ def test_contains_dn(self):
+ self.assertIn('dn', self.msg)
+
+ def test_contains_dn_case(self):
+ self.assertIn('DN', self.msg)
+
+ def test_contains_invalid(self):
+ self.assertRaises(TypeError, lambda: None in self.msg)
+
+ def test_msg_diff(self):
+ l = ldb.Ldb()
+ msgs = l.parse_ldif("dn: foo=bar\nfoo: bar\nbaz: do\n\ndn: foo=bar\nfoo: bar\nbaz: dont\n")
+ msg1 = next(msgs)[1]
+ msg2 = next(msgs)[1]
+ msgdiff = l.msg_diff(msg1, msg2)
+ self.assertEqual("foo=bar", msgdiff.get("dn").__str__())
+ self.assertRaises(KeyError, lambda: msgdiff["foo"])
+ self.assertEqual(1, len(msgdiff))
+
+ def test_equal_empty(self):
+ msg1 = ldb.Message()
+ msg2 = ldb.Message()
+ self.assertEqual(msg1, msg2)
+
+ def test_equal_simplel(self):
+ db = ldb.Ldb()
+ msg1 = ldb.Message()
+ msg1.dn = ldb.Dn(db, "foo=bar")
+ msg2 = ldb.Message()
+ msg2.dn = ldb.Dn(db, "foo=bar")
+ self.assertEqual(msg1, msg2)
+ msg1['foo'] = b'bar'
+ msg2['foo'] = b'bar'
+ self.assertEqual(msg1, msg2)
+ msg2['foo'] = b'blie'
+ self.assertNotEqual(msg1, msg2)
+ msg2['foo'] = b'blie'
+
+ def test_from_dict(self):
+ rec = {"dn": "dc=fromdict",
+ "a1": [b"a1-val1", b"a1-val1"]}
+ l = ldb.Ldb()
+ # check different types of input Flags
+ for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]:
+ m = ldb.Message.from_dict(l, rec, flags)
+ self.assertEqual(rec["a1"], list(m["a1"]))
+ self.assertEqual(flags, m["a1"].flags())
+ # check input params
+ self.assertRaises(TypeError, ldb.Message.from_dict, dict(), rec, ldb.FLAG_MOD_REPLACE)
+ self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE)
+ self.assertRaises(ValueError, ldb.Message.from_dict, l, rec, 0)
+ # Message.from_dict expects dictionary with 'dn'
+ err_rec = {"a1": [b"a1-val1", b"a1-val1"]}
+ self.assertRaises(TypeError, ldb.Message.from_dict, l, err_rec, ldb.FLAG_MOD_REPLACE)
+
+ def test_from_dict_text(self):
+ rec = {"dn": "dc=fromdict",
+ "a1": ["a1-val1", "a1-val1"]}
+ l = ldb.Ldb()
+ # check different types of input Flags
+ for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]:
+ m = ldb.Message.from_dict(l, rec, flags)
+ self.assertEqual(rec["a1"], list(m.text["a1"]))
+ self.assertEqual(flags, m.text["a1"].flags())
+ # check input params
+ self.assertRaises(TypeError, ldb.Message.from_dict, dict(), rec, ldb.FLAG_MOD_REPLACE)
+ self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE)
+ self.assertRaises(ValueError, ldb.Message.from_dict, l, rec, 0)
+ # Message.from_dict expects dictionary with 'dn'
+ err_rec = {"a1": ["a1-val1", "a1-val1"]}
+ self.assertRaises(TypeError, ldb.Message.from_dict, l, err_rec, ldb.FLAG_MOD_REPLACE)
+
+ def test_copy_add_message_element(self):
+ m = ldb.Message()
+ m["1"] = ldb.MessageElement([b"val 111"], ldb.FLAG_MOD_ADD, "1")
+ m["2"] = ldb.MessageElement([b"val 222"], ldb.FLAG_MOD_ADD, "2")
+ mto = ldb.Message()
+ mto["1"] = m["1"]
+ mto["2"] = m["2"]
+ self.assertEqual(mto["1"], m["1"])
+ self.assertEqual(mto["2"], m["2"])
+ mto = ldb.Message()
+ mto.add(m["1"])
+ mto.add(m["2"])
+ self.assertEqual(mto["1"], m["1"])
+ self.assertEqual(mto["2"], m["2"])
+
+ def test_copy_add_message_element_text(self):
+ m = ldb.Message()
+ m["1"] = ldb.MessageElement(["val 111"], ldb.FLAG_MOD_ADD, "1")
+ m["2"] = ldb.MessageElement(["val 222"], ldb.FLAG_MOD_ADD, "2")
+ mto = ldb.Message()
+ mto["1"] = m["1"]
+ mto["2"] = m["2"]
+ self.assertEqual(mto["1"], m.text["1"])
+ self.assertEqual(mto["2"], m.text["2"])
+ mto = ldb.Message()
+ mto.add(m["1"])
+ mto.add(m["2"])
+ self.assertEqual(mto.text["1"], m.text["1"])
+ self.assertEqual(mto.text["2"], m.text["2"])
+ self.assertEqual(mto["1"], m["1"])
+ self.assertEqual(mto["2"], m["2"])
+
+
+class MessageElementTests(TestCase):
+
+ def test_cmp_element(self):
+ x = ldb.MessageElement([b"foo"])
+ y = ldb.MessageElement([b"foo"])
+ z = ldb.MessageElement([b"bzr"])
+ self.assertEqual(x, y)
+ self.assertNotEqual(x, z)
+
+ def test_cmp_element_text(self):
+ x = ldb.MessageElement([b"foo"])
+ y = ldb.MessageElement(["foo"])
+ self.assertEqual(x, y)
+
+ def test_create_iterable(self):
+ x = ldb.MessageElement([b"foo"])
+ self.assertEqual([b"foo"], list(x))
+ self.assertEqual(["foo"], list(x.text))
+
+ def test_repr(self):
+ x = ldb.MessageElement([b"foo"])
+ self.assertEqual("MessageElement([b'foo'])", repr(x))
+ self.assertEqual("MessageElement([b'foo']).text", repr(x.text))
+ x = ldb.MessageElement([b"foo", b"bla"])
+ self.assertEqual(2, len(x))
+ self.assertEqual("MessageElement([b'foo',b'bla'])", repr(x))
+ self.assertEqual("MessageElement([b'foo',b'bla']).text", repr(x.text))
+
+ def test_get_item(self):
+ x = ldb.MessageElement([b"foo", b"bar"])
+ self.assertEqual(b"foo", x[0])
+ self.assertEqual(b"bar", x[1])
+ self.assertEqual(b"bar", x[-1])
+ self.assertRaises(IndexError, lambda: x[45])
+
+ def test_get_item_text(self):
+ x = ldb.MessageElement(["foo", "bar"])
+ self.assertEqual("foo", x.text[0])
+ self.assertEqual("bar", x.text[1])
+ self.assertEqual("bar", x.text[-1])
+ self.assertRaises(IndexError, lambda: x[45])
+
+ def test_len(self):
+ x = ldb.MessageElement([b"foo", b"bar"])
+ self.assertEqual(2, len(x))
+
+ def test_eq(self):
+ x = ldb.MessageElement([b"foo", b"bar"])
+ y = ldb.MessageElement([b"foo", b"bar"])
+ self.assertEqual(y, x)
+ x = ldb.MessageElement([b"foo"])
+ self.assertNotEqual(y, x)
+ y = ldb.MessageElement([b"foo"])
+ self.assertEqual(y, x)
+
+ def test_extended(self):
+ el = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+ self.assertEqual("MessageElement([b'456'])", repr(el))
+ self.assertEqual("MessageElement([b'456']).text", repr(el.text))
+
+ def test_bad_text(self):
+ el = ldb.MessageElement(b'\xba\xdd')
+ self.assertRaises(UnicodeDecodeError, el.text.__getitem__, 0)
+
+
+class ModuleTests(TestCase):
+
+ def setUp(self):
+ super(ModuleTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ self.ldb = ldb.Ldb(self.filename)
+
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(ModuleTests, self).setUp()
+
+ def test_register_module(self):
+ class ExampleModule:
+ name = "example"
+ ldb.register_module(ExampleModule)
+
+ def test_use_module(self):
+ ops = []
+
+ class ExampleModule:
+ name = "bla"
+
+ def __init__(self, ldb, next):
+ ops.append("init")
+ self.next = next
+
+ def search(self, *args, **kwargs):
+ return self.next.search(*args, **kwargs)
+
+ def request(self, *args, **kwargs):
+ pass
+
+ ldb.register_module(ExampleModule)
+ l = ldb.Ldb(self.filename)
+ l.add({"dn": "@MODULES", "@LIST": "bla"})
+ self.assertEqual([], ops)
+ l = ldb.Ldb(self.filename)
+ self.assertEqual(["init"], ops)
+
+
+class LdbResultTests(LdbBaseTest):
+
+ def setUp(self):
+ super(LdbResultTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ self.l = ldb.Ldb(self.url(), flags=self.flags())
+ try:
+ self.l.add(self.index)
+ except AttributeError:
+ pass
+ self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": b"samba.org",
+ "objectUUID": b"0123456789abcde0"})
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": b"Admins",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": b"Users",
+ "objectUUID": b"0123456789abcde2"})
+ self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", "name": b"OU #1",
+ "objectUUID": b"0123456789abcde3"})
+ self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", "name": b"OU #2",
+ "objectUUID": b"0123456789abcde4"})
+ self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", "name": b"OU #3",
+ "objectUUID": b"0123456789abcde5"})
+ self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", "name": b"OU #4",
+ "objectUUID": b"0123456789abcde6"})
+ self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", "name": b"OU #5",
+ "objectUUID": b"0123456789abcde7"})
+ self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", "name": b"OU #6",
+ "objectUUID": b"0123456789abcde8"})
+ self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", "name": b"OU #7",
+ "objectUUID": b"0123456789abcde9"})
+ self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", "name": b"OU #8",
+ "objectUUID": b"0123456789abcdea"})
+ self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", "name": b"OU #9",
+ "objectUUID": b"0123456789abcdeb"})
+ self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", "name": b"OU #10",
+ "objectUUID": b"0123456789abcdec"})
+
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(LdbResultTests, self).tearDown()
+ # Ensure the LDB is closed now, so we close the FD
+ del(self.l)
+
+ def test_return_type(self):
+ res = self.l.search()
+ self.assertEqual(str(res), "<ldb result>")
+
+ def test_get_msgs(self):
+ res = self.l.search()
+ list = res.msgs
+
+ def test_get_controls(self):
+ res = self.l.search()
+ list = res.controls
+
+ def test_get_referals(self):
+ res = self.l.search()
+ list = res.referals
+
+ def test_iter_msgs(self):
+ found = False
+ for l in self.l.search().msgs:
+ if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG":
+ found = True
+ self.assertTrue(found)
+
+ def test_iter_msgs_count(self):
+ self.assertTrue(self.l.search().count > 0)
+ # 13 objects has been added to the DC=SAMBA, DC=ORG
+ self.assertEqual(self.l.search(base="DC=SAMBA,DC=ORG").count, 13)
+
+ def test_iter_controls(self):
+ res = self.l.search().controls
+ it = iter(res)
+
+ def test_create_control(self):
+ self.assertRaises(ValueError, ldb.Control, self.l, "tatayoyo:0")
+ c = ldb.Control(self.l, "relax:1")
+ self.assertEqual(c.critical, True)
+ self.assertEqual(c.oid, "1.3.6.1.4.1.4203.666.5.12")
+
+ def test_iter_refs(self):
+ res = self.l.search().referals
+ it = iter(res)
+
+ def test_search_sequence_msgs(self):
+ found = False
+ res = self.l.search().msgs
+
+ for i in range(0, len(res)):
+ l = res[i]
+ if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG":
+ found = True
+ self.assertTrue(found)
+
+ def test_search_as_iter(self):
+ found = False
+ res = self.l.search()
+
+ for l in res:
+ if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG":
+ found = True
+ self.assertTrue(found)
+
+ def test_search_iter(self):
+ found = False
+ res = self.l.search_iterator()
+
+ for l in res:
+ if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG":
+ found = True
+ self.assertTrue(found)
+
+ # Show that search results can't see into a transaction
+
+ def test_search_against_trans(self):
+ found11 = False
+
+ (r1, w1) = os.pipe()
+
+ (r2, w2) = os.pipe()
+
+ # For the first element, fork a child that will
+ # write to the DB
+ pid = os.fork()
+ if pid == 0:
+ # In the child, re-open
+ del(self.l)
+ gc.collect()
+
+ child_ldb = ldb.Ldb(self.url(), flags=self.flags())
+ # start a transaction
+ child_ldb.transaction_start()
+
+ # write to it
+ child_ldb.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG",
+ "name": b"samba.org",
+ "objectUUID": b"o123456789acbdef"})
+
+ os.write(w1, b"added")
+
+ # Now wait for the search to be done
+ os.read(r2, 6)
+
+ # and commit
+ try:
+ child_ldb.transaction_commit()
+ except ldb.LdbError as err:
+ # We print this here to see what went wrong in the child
+ print(err)
+ os._exit(1)
+
+ os.write(w1, b"transaction")
+ os._exit(0)
+
+ self.assertEqual(os.read(r1, 5), b"added")
+
+ # This should not turn up until the transaction is concluded
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 0)
+
+ os.write(w2, b"search")
+
+ # Now wait for the transaction to be done. This should
+ # deadlock, but the search doesn't hold a read lock for the
+ # iterator lifetime currently.
+ self.assertEqual(os.read(r1, 11), b"transaction")
+
+ # This should now turn up, as the transaction is over
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 1)
+
+ self.assertFalse(found11)
+
+ (got_pid, status) = os.waitpid(pid, 0)
+ self.assertEqual(got_pid, pid)
+
+ def test_search_iter_against_trans(self):
+ found = False
+ found11 = False
+
+ # We need to hold this iterator open to hold the all-record
+ # lock
+ res = self.l.search_iterator()
+
+ (r1, w1) = os.pipe()
+
+ (r2, w2) = os.pipe()
+
+ # For the first element, with the sequence open (which
+ # means with ldb locks held), fork a child that will
+ # write to the DB
+ pid = os.fork()
+ if pid == 0:
+ # In the child, re-open
+ del(res)
+ del(self.l)
+ gc.collect()
+
+ child_ldb = ldb.Ldb(self.url(), flags=self.flags())
+ # start a transaction
+ child_ldb.transaction_start()
+
+ # write to it
+ child_ldb.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG",
+ "name": b"samba.org",
+ "objectUUID": b"o123456789acbdef"})
+
+ os.write(w1, b"added")
+
+ # Now wait for the search to be done
+ os.read(r2, 6)
+
+ # and commit
+ try:
+ child_ldb.transaction_commit()
+ except ldb.LdbError as err:
+ # We print this here to see what went wrong in the child
+ print(err)
+ os._exit(1)
+
+ os.write(w1, b"transaction")
+ os._exit(0)
+
+ self.assertEqual(os.read(r1, 5), b"added")
+
+ # This should not turn up until the transaction is concluded
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 0)
+
+ os.write(w2, b"search")
+
+ # allow the transaction to start
+ time.sleep(1)
+
+ # This should not turn up until the search finishes and
+ # removed the read lock, but for ldb_tdb that happened as soon
+ # as we called the first res.next()
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 0)
+
+ # These results are all collected at the first next(res) call
+ for l in res:
+ if str(l.dn) == "OU=OU10,DC=SAMBA,DC=ORG":
+ found = True
+ if str(l.dn) == "OU=OU11,DC=SAMBA,DC=ORG":
+ found11 = True
+
+ # Now wait for the transaction to be done.
+ self.assertEqual(os.read(r1, 11), b"transaction")
+
+ # This should now turn up, as the transaction is over and all
+ # read locks are gone
+ res11 = self.l.search(base="OU=OU11,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res11), 1)
+
+ self.assertTrue(found)
+ self.assertFalse(found11)
+
+ (got_pid, status) = os.waitpid(pid, 0)
+ self.assertEqual(got_pid, pid)
+
+
+class LdbResultTestsLmdb(LdbResultTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(LdbResultTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(LdbResultTestsLmdb, self).tearDown()
+
+
+class BadTypeTests(TestCase):
+ def test_control(self):
+ l = ldb.Ldb()
+ self.assertRaises(TypeError, ldb.Control, '<bad type>', 'relax:1')
+ self.assertRaises(TypeError, ldb.Control, ldb, 1234)
+
+ def test_modify(self):
+ l = ldb.Ldb()
+ dn = ldb.Dn(l, 'a=b')
+ m = ldb.Message(dn)
+ self.assertRaises(TypeError, l.modify, '<bad type>')
+ self.assertRaises(TypeError, l.modify, m, '<bad type>')
+
+ def test_add(self):
+ l = ldb.Ldb()
+ dn = ldb.Dn(l, 'a=b')
+ m = ldb.Message(dn)
+ self.assertRaises(TypeError, l.add, '<bad type>')
+ self.assertRaises(TypeError, l.add, m, '<bad type>')
+
+ def test_delete(self):
+ l = ldb.Ldb()
+ dn = ldb.Dn(l, 'a=b')
+ self.assertRaises(TypeError, l.add, '<bad type>')
+ self.assertRaises(TypeError, l.add, dn, '<bad type>')
+
+ def test_rename(self):
+ l = ldb.Ldb()
+ dn = ldb.Dn(l, 'a=b')
+ self.assertRaises(TypeError, l.add, '<bad type>', dn)
+ self.assertRaises(TypeError, l.add, dn, '<bad type>')
+ self.assertRaises(TypeError, l.add, dn, dn, '<bad type>')
+
+ def test_search(self):
+ l = ldb.Ldb()
+ self.assertRaises(TypeError, l.search, base=1234)
+ self.assertRaises(TypeError, l.search, scope='<bad type>')
+ self.assertRaises(TypeError, l.search, expression=1234)
+ self.assertRaises(TypeError, l.search, attrs='<bad type>')
+ self.assertRaises(TypeError, l.search, controls='<bad type>')
+
+
+class VersionTests(TestCase):
+
+ def test_version(self):
+ self.assertTrue(isinstance(ldb.__version__, str))
+
+class NestedTransactionTests(LdbBaseTest):
+ def setUp(self):
+ super(NestedTransactionTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "test.ldb")
+ self.ldb = ldb.Ldb(self.url(), flags=self.flags())
+ self.ldb.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]})
+
+ super(NestedTransactionTests, self).setUp()
+
+ #
+ # This test documents that currently ldb does not support true nested
+ # transactions.
+ #
+ # Note: The test is written so that it treats failure as pass.
+ # It is done this way as standalone ldb builds do not use the samba
+ # known fail mechanism
+ #
+ def test_nested_transactions(self):
+
+ self.ldb.transaction_start()
+
+ self.ldb.add({"dn": "x=x1,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde1"})
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde1)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+
+ self.ldb.add({"dn": "x=x2,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde2"})
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde2)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+
+ self.ldb.transaction_start()
+ self.ldb.add({"dn": "x=x3,dc=samba,dc=org",
+ "objectUUID": b"0123456789abcde3"})
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde3)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+ self.ldb.transaction_cancel()
+ #
+ # Check that we can not see the record added by the cancelled
+ # transaction.
+ # Currently this fails as ldb does not support true nested
+ # transactions, and only the outer commits and cancels have an effect
+ #
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde3)",
+ base="dc=samba,dc=org")
+ #
+ # FIXME this test currently passes on a failure, i.e. if nested
+ # transaction support worked correctly the correct test would
+ # be.
+ # self.assertEqual(len(res), 0)
+ # as the add of objectUUID=0123456789abcde3 would reverted when
+ # the sub transaction it was nested in was rolled back.
+ #
+ # Currently this is not the case so the record is still present.
+ self.assertEqual(len(res), 1)
+
+
+ # Commit the outer transaction
+ #
+ self.ldb.transaction_commit()
+ #
+ # Now check we can still see the records added in the outer
+ # transaction.
+ #
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde1)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde2)",
+ base="dc=samba,dc=org")
+ self.assertEqual(len(res), 1)
+ #
+ # And that we can't see the records added by the nested transaction.
+ #
+ res = self.ldb.search(expression="(objectUUID=0123456789abcde3)",
+ base="dc=samba,dc=org")
+ # FIXME again if nested transactions worked correctly we would not
+ # see this record. The test should be.
+ # self.assertEqual(len(res), 0)
+ self.assertEqual(len(res), 1)
+
+ def tearDown(self):
+ super(NestedTransactionTests, self).tearDown()
+
+
+class LmdbNestedTransactionTests(NestedTransactionTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(LmdbNestedTransactionTests, self).setUp()
+
+ def tearDown(self):
+ super(LmdbNestedTransactionTests, self).tearDown()
+
+
+if __name__ == '__main__':
+ import unittest
+ unittest.TestProgram()
diff --git a/lib/ldb/tests/python/crash.py b/lib/ldb/tests/python/crash.py
new file mode 100644
index 0000000..3283981
--- /dev/null
+++ b/lib/ldb/tests/python/crash.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+#
+# Tests for crashing functions
+
+import os
+from unittest import TestCase
+import os
+import sys
+import traceback
+
+import ldb
+
+
+def segfault_detector(f):
+ def wrapper(*args, **kwargs):
+ pid = os.fork()
+ if pid == 0:
+ # child, crashing?
+ try:
+ f(*args, **kwargs)
+ except Exception as e:
+ traceback.print_exc()
+ sys.stderr.flush()
+ sys.stdout.flush()
+ os._exit(0)
+
+ # parent, waiting
+ pid2, status = os.waitpid(pid, 0)
+ if os.WIFSIGNALED(status):
+ signal = os.WTERMSIG(status)
+ raise AssertionError("Failed with signal %d" % signal)
+
+ return wrapper
+
+
+class LdbDnCrashTests(TestCase):
+ @segfault_detector
+ def test_ldb_dn_explode_crash(self):
+ for i in range(106, 150):
+ dn = ldb.Dn(ldb.Ldb(), "a=b%s,c= " % (' ' * i))
+ dn.validate()
+
+if __name__ == '__main__':
+ import unittest
+ unittest.TestProgram()
diff --git a/lib/ldb/tests/python/index.py b/lib/ldb/tests/python/index.py
new file mode 100755
index 0000000..c1da76d
--- /dev/null
+++ b/lib/ldb/tests/python/index.py
@@ -0,0 +1,1454 @@
+#!/usr/bin/env python3
+#
+# Tests for truncated index keys
+#
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""Tests for truncated index keys
+
+Databases such as lmdb have a maximum key length, these tests ensure that
+ldb behaves correctly in those circumstances.
+
+"""
+
+import os
+from unittest import TestCase
+import sys
+import ldb
+import shutil
+
+
+TDB_PREFIX = "tdb://"
+MDB_PREFIX = "mdb://"
+
+
+def tempdir():
+ import tempfile
+ try:
+ dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp")
+ except KeyError:
+ dir_prefix = None
+ return tempfile.mkdtemp(dir=dir_prefix)
+
+
+def contains(result, dn):
+ if result is None:
+ return False
+
+ for r in result:
+ if str(r["dn"]) == dn:
+ return True
+ return False
+
+
+class LdbBaseTest(TestCase):
+ def setUp(self):
+ super(LdbBaseTest, self).setUp()
+ try:
+ if self.prefix is None:
+ self.prefix = TDB_PREFIX
+ except AttributeError:
+ self.prefix = TDB_PREFIX
+
+ def tearDown(self):
+ super(LdbBaseTest, self).tearDown()
+
+ def url(self):
+ return self.prefix + self.filename
+
+ def flags(self):
+ if self.prefix == MDB_PREFIX:
+ return ldb.FLG_NOSYNC
+ else:
+ return 0
+
+
+class MaxIndexKeyLengthTests(LdbBaseTest):
+ def checkGuids(self, key, guids):
+ #
+ # This check relies on the current implementation where the indexes
+ # are in the same database as the data.
+ #
+ # It checks that the index record exists, unless guids is None then
+ # the record must not exist. And the it contains the expected guid
+ # entries.
+ #
+ # The caller needs to provide the GUID's in the expected order
+ #
+ res = self.l.search(
+ base=key,
+ scope=ldb.SCOPE_BASE)
+ if guids is None:
+ self.assertEqual(len(res), 0)
+ return
+ self.assertEqual(len(res), 1)
+
+ # The GUID index format has only one value
+ index = res[0]["@IDX"][0]
+ self.assertEqual(len(guids), len(index))
+ self.assertEqual(guids, index)
+
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(MaxIndexKeyLengthTests, self).tearDown()
+
+ # Ensure the LDB is closed now, so we close the FD
+ del(self.l)
+
+ def setUp(self):
+ super(MaxIndexKeyLengthTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "key_len_test.ldb")
+ # Note that the maximum key length is set to 54
+ # This accounts for the 4 bytes added by the dn formatting
+ # a leading dn=, and a trailing zero terminator
+ #
+ self.l = ldb.Ldb(self.url(),
+ options=[
+ "modules:rdn_name",
+ "max_key_len_for_self_test:54"])
+ self.l.add({"dn": "@ATTRIBUTES",
+ "uniqueThing": "UNIQUE_INDEX"})
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [
+ b"uniqueThing",
+ b"notUnique",
+ b"base64____lt",
+ b"base64_____eq",
+ b"base64______gt"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]})
+
+ # Add a value to a unique index that exceeds the maximum key length
+ # This should be rejected.
+ def test_add_long_unique_add(self):
+ try:
+ self.l.add({"dn": "OU=UNIQUE_MAX_LEN,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef",
+ "uniqueThing": "01234567890123456789012345678901"})
+ # index key will be
+ # "@INDEX:UNIQUETHING:01234567890123456789012345678901"
+ self.fail("Should have failed on long index key")
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION)
+
+ # Test that DN's longer the maximum key length can be added
+ # and that duplicate DN's are rejected correctly
+ def test_add_long_dn_add(self):
+ #
+ # For all entries the DN index key gets truncated to
+ # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
+ #
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
+ "objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcde1" + b"0123456789abcdef")
+
+ # Key is equal to max length does not get inserted into the truncated
+ # key namespace
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ # This key should not get truncated, as it's one character less than
+ # max, and will not be in the truncate name space
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde7"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde7")
+
+ try:
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcde2"})
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ try:
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
+ "objectUUID": b"0123456789abcde3"})
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ try:
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ "objectUUID": b"0123456789abcde4"})
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ try:
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde6"})
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ try:
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde8"})
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ def test_rename_truncated_dn_keys(self):
+ # For all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
+ "objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcdef")
+
+ # Non conflicting rename, should succeed
+ self.l.rename("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ # Index should be unchanged.
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcdef")
+
+ # Conflicting rename should fail
+ try:
+ self.l.rename("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
+ "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_ENTRY_ALREADY_EXISTS)
+
+ def test_delete_truncated_dn_keys(self):
+ #
+ # For all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
+ #
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ # Try to delete a non existent DN with a truncated key
+ try:
+ self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM")
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_NO_SUCH_OBJECT)
+ # Ensure that non of the other truncated DN's got deleted
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 1)
+
+ # Ensure that the non truncated DN did not get deleted
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 1)
+
+ # Check the indexes are correct
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ # delete an existing entry
+ self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG")
+
+ # Ensure it got deleted
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG")
+ self.assertEqual(len(res), 0)
+
+ # Ensure that non of the other truncated DN's got deleted
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 1)
+
+ # Ensure the non truncated entry did not get deleted.
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 1)
+
+ # Check the indexes are correct
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ # delete an existing entry
+ self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+
+ # Ensure it got deleted
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 0)
+
+ # Ensure that non of the non truncated DN's got deleted
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 1)
+ # Check the indexes are correct
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ None)
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ # delete an existing entry
+ self.l.delete("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA")
+
+ # Ensure it got deleted
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBAxxx")
+ self.assertEqual(len(res), 0)
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ None)
+
+ def test_search_truncated_dn_keys(self):
+ #
+ # For all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
+ #
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM")
+ self.assertEqual(len(res), 0)
+
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 0)
+
+ # Non existent, key one less than truncation limit
+ res = self.l.search(base="OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 0)
+
+ def test_search_dn_filter_truncated_dn_keys(self):
+ #
+ # For all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
+ #
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ res = self.l.search(
+ expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM")
+ self.assertEqual(len(res), 0)
+
+ res = self.l.search(
+ expression="dn=OU=A_LONG_DNXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ self.assertEqual(len(res), 0)
+
+ # Non existent, key one less than truncation limit
+ res = self.l.search(
+ expression="dn=OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA")
+ self.assertEqual(len(res), 0)
+
+ def test_search_one_level_truncated_dn_keys(self):
+ #
+ # Except for the base DN's
+ # all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=??,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA
+ # The base DN-s truncate to
+ # @INDEX:@IDXDN:OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR
+ #
+ self.l.add({"dn": "OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcdef"})
+ self.l.add({"dn": "OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcd1f"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR",
+ b"0123456789abcd1f" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcd11"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcd11" + b"0123456789abcde1")
+
+ self.l.add({"dn": "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde2"})
+ self.l.add({"dn": "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcdf2"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcde2" + b"0123456789abcdf2")
+
+ self.l.add({"dn": "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde3"})
+ self.l.add({"dn": "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcd13"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcd13" + b"0123456789abcde3")
+
+ # This key is not truncated as it's the max_key_len
+ self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde7"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcde7")
+
+ res = self.l.search(base="OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
+ scope=ldb.SCOPE_ONELEVEL)
+ self.assertEqual(len(res), 3)
+ self.assertTrue(
+ contains(res, "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1"))
+
+ res = self.l.search(base="OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
+ scope=ldb.SCOPE_ONELEVEL)
+ self.assertEqual(len(res), 3)
+ self.assertTrue(
+ contains(res, "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2"))
+
+ res = self.l.search(base="OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ scope=ldb.SCOPE_ONELEVEL)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA"))
+
+ def test_search_sub_tree_truncated_dn_keys(self):
+ #
+ # Except for the base DN's
+ # all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=??,OU=A_LONG_DN_SUB_TREE,DC=SAMBA
+ # The base DN-s truncate to
+ # @INDEX:@IDXDN:OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR
+ #
+ self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcdef"})
+ self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcde4"})
+ self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3",
+ "objectUUID": b"0123456789abcde8"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR",
+ b"0123456789abcde4" + b"0123456789abcde8" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcde5")
+
+ self.l.add({"dn": "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde2"})
+ self.l.add({"dn": "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcde6"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde2" + b"0123456789abcde6")
+
+ self.l.add({"dn": "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde3"})
+
+ self.l.add({"dn": "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcde7"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde3" + b"0123456789abcde7")
+
+ self.l.add({"dn": "OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4",
+ "objectUUID": b"0123456789abcde9"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde9")
+
+ res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
+ scope=ldb.SCOPE_SUBTREE)
+ self.assertEqual(len(res), 4)
+ self.assertTrue(
+ contains(res, "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1"))
+
+ res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
+ scope=ldb.SCOPE_SUBTREE)
+ self.assertEqual(len(res), 4)
+ self.assertTrue(
+ contains(res, "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2"))
+
+ res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3",
+ scope=ldb.SCOPE_SUBTREE)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3"))
+
+ res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4",
+ scope=ldb.SCOPE_SUBTREE)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4"))
+
+ def test_search_base_truncated_dn_keys(self):
+ #
+ # For all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
+ #
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ "objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde5")
+
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res), 1)
+
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res), 0)
+
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res), 0)
+
+ # Non existent, key one less than truncation limit
+ res = self.l.search(
+ base="OU=A_LONG_DNXXXXXXXXXXXXXX,DC=SAMBA",
+ scope=ldb.SCOPE_BASE)
+ self.assertEqual(len(res), 0)
+
+ #
+ # Test non unique index searched with truncated keys
+ #
+ def test_index_truncated_keys(self):
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+ eq_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ lt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ # > than max length and differs in values that will be truncated
+ gt_max_b = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
+
+ # Add two entries with the same value, key length = max so no
+ # truncation.
+ self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": eq_max,
+ "objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0")
+
+ self.l.add({"dn": "OU=02,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": eq_max,
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde1")
+
+ #
+ # An entry outside the tree
+ #
+ self.l.add({"dn": "OU=10,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG",
+ "notUnique": eq_max,
+ "objectUUID": b"0123456789abcd11"})
+ self.checkGuids(
+ "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcd11" + b"0123456789abcde0" + b"0123456789abcde1")
+
+ # Key longer than max so should get truncated to same key as
+ # the previous two entries
+ self.l.add({"dn": "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": gt_max,
+ "objectUUID": b"0123456789abcde2"})
+ # But in the truncated key space
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde2")
+
+ # Key longer than max so should get truncated to same key as
+ # the previous entries but differs in the chars after max length
+ self.l.add({"dn": "OU=23,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": gt_max_b,
+ "objectUUID": b"0123456789abcd22"})
+ # But in the truncated key space
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcd22" + b"0123456789abcde2")
+ #
+ # An entry outside the tree
+ #
+ self.l.add({"dn": "OU=11,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG",
+ "notUnique": gt_max,
+ "objectUUID": b"0123456789abcd12"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcd12" + b"0123456789abcd22" + b"0123456789abcde2")
+
+ # Key shorter than max
+ #
+ self.l.add({"dn": "OU=04,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": lt_max,
+ "objectUUID": b"0123456789abcde3"})
+ self.checkGuids(
+ "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde3")
+ #
+ # An entry outside the tree
+ #
+ self.l.add({"dn": "OU=12,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG",
+ "notUnique": lt_max,
+ "objectUUID": b"0123456789abcd13"})
+ self.checkGuids(
+ "@INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcd13" + b"0123456789abcde3")
+
+ #
+ # search for target is max value not truncated
+ # should return ou's 01, 02
+ #
+ expression = "(notUnique=" + eq_max.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 2)
+ self.assertTrue(
+ contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ #
+ # search for target is max value not truncated
+ # search one level up the tree, scope is ONE_LEVEL
+ # So should get no matches
+ #
+ expression = "(notUnique=" + eq_max.decode('ascii') + ")"
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 0)
+ #
+ # search for target is max value not truncated
+ # search one level up the tree, scope is SUBTREE
+ # So should get 3 matches
+ #
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression=expression)
+ self.assertEqual(len(res), 3)
+ self.assertTrue(
+ contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=10,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG"))
+ #
+ # search for target is max value + 1 so truncated
+ # should return ou 23 as it's gt_max_b being searched for
+ #
+ expression = "(notUnique=" + gt_max_b.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=23,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ #
+ # search for target is max value + 1 so truncated
+ # should return ou 03 as it's gt_max being searched for
+ #
+ expression = "(notUnique=" + gt_max.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ #
+ # scope one level and one level up one level up should get no matches
+ #
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 0)
+ #
+ # scope sub tree and one level up one level up should get 2 matches
+ #
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression=expression)
+ self.assertEqual(len(res), 2)
+ self.assertTrue(
+ contains(res, "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=11,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG"))
+
+ #
+ # search for target is max value - 1 so not truncated
+ # should return ou 04
+ #
+ expression = "(notUnique=" + lt_max.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=04,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ #
+ # scope one level and one level up one level up should get no matches
+ #
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 0)
+
+ #
+ # scope sub tree and one level up one level up should get 2 matches
+ #
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression=expression)
+ self.assertEqual(len(res), 2)
+ self.assertTrue(
+ contains(res, "OU=04,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=12,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG"))
+
+ #
+ # Test index key truncation for base64 encoded values
+ #
+ def test_index_truncated_base64_encoded_keys(self):
+ value = b"aaaaaaaaaaaaaaaaaaaa\x02"
+ # base64 encodes to "YWFhYWFhYWFhYWFhYWFhYWFhYWEC"
+
+ # One less than max key length
+ self.l.add({"dn": "OU=01,OU=BASE64,DC=SAMBA,DC=ORG",
+ "base64____lt": value,
+ "objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX:BASE64____LT::YWFhYWFhYWFhYWFhYWFhYWFhYWEC",
+ b"0123456789abcde0")
+
+ # Equal max key length
+ self.l.add({"dn": "OU=02,OU=BASE64,DC=SAMBA,DC=ORG",
+ "base64_____eq": value,
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX:BASE64_____EQ::YWFhYWFhYWFhYWFhYWFhYWFhYWEC",
+ b"0123456789abcde1")
+
+ # One greater than max key length
+ self.l.add({"dn": "OU=03,OU=BASE64,DC=SAMBA,DC=ORG",
+ "base64______gt": value,
+ "objectUUID": b"0123456789abcde2"})
+ self.checkGuids(
+ "@INDEX#BASE64______GT##YWFhYWFhYWFhYWFhYWFhYWFhYWE",
+ b"0123456789abcde2")
+ #
+ # Test adding to non unique index with identical multivalued index
+ # attributes
+ #
+
+ def test_index_multi_valued_identical_keys(self):
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ as_eq_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ bs_eq_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+ try:
+ self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": [bs_eq_max, as_eq_max, as_eq_max],
+ "objectUUID": b"0123456789abcde0"})
+ self.fail("Exception not thrown")
+ except ldb.LdbError as e:
+ code = e.args[0]
+ self.assertEqual(ldb.ERR_ATTRIBUTE_OR_VALUE_EXISTS, code)
+
+ #
+ # Test non unique index with multivalued index attributes
+ # searched with non truncated keys
+ #
+ def test_search_index_multi_valued_truncated_keys(self):
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+ aa_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ ab_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
+ bb_gt_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+ self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": [aa_gt_max, ab_gt_max, bb_gt_max],
+ "objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+
+ expression = "(notUnique=" + aa_gt_max.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ expression = "(notUnique=" + ab_gt_max.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ expression = "(notUnique=" + bb_gt_max.decode('ascii') + ")"
+ res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression=expression)
+ self.assertEqual(len(res), 1)
+ self.assertTrue(
+ contains(res, "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ #
+ # Test deletion of records with non unique index with multivalued index
+ # attributes
+ # replicate this to test modify with modify flags i.e. DELETE, REPLACE
+ #
+ def test_delete_index_multi_valued_truncated_keys(self):
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+ aa_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ ab_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
+ bb_gt_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ cc_gt_max = b"cccccccccccccccccccccccccccccccccc"
+
+ self.l.add({"dn": "OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": [aa_gt_max, ab_gt_max, bb_gt_max],
+ "objectUUID": b"0123456789abcde0"})
+ self.l.add({"dn": "OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": [aa_gt_max, ab_gt_max, cc_gt_max],
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ res = self.l.search(
+ base="DC=SAMBA,DC=ORG",
+ expression="(notUnique=" + aa_gt_max.decode("ascii") + ")")
+ self.assertEqual(2, len(res))
+ self.assertTrue(
+ contains(res, "OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ self.l.delete("OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ None)
+
+ self.l.delete("OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ None)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ None)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ None)
+
+ #
+ # Test modification of records with non unique index with multivalued index
+ # attributes
+ #
+ def test_modify_index_multi_valued_truncated_keys(self):
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:NOTUNIQUE:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+ aa_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ ab_gt_max = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
+ bb_gt_max = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ cc_gt_max = b"cccccccccccccccccccccccccccccccccc"
+
+ self.l.add({"dn": "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": [aa_gt_max, ab_gt_max, bb_gt_max],
+ "objectUUID": b"0123456789abcde0"})
+ self.l.add({"dn": "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG",
+ "notUnique": [aa_gt_max, ab_gt_max, cc_gt_max],
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ res = self.l.search(
+ base="DC=SAMBA,DC=ORG",
+ expression="(notUnique=" + aa_gt_max.decode("ascii") + ")")
+ self.assertEqual(2, len(res))
+ self.assertTrue(
+ contains(res, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG"))
+
+ #
+ # Modify that does not change the indexed attribute
+ #
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self.l, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ msg["notUnique"] = ldb.MessageElement(
+ [aa_gt_max, ab_gt_max, bb_gt_max],
+ ldb.FLAG_MOD_REPLACE,
+ "notUnique")
+ self.l.modify(msg)
+ #
+ # As the modify is replacing the attribute with the same contents
+ # there should be no changes to the indexes.
+ #
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ #
+ # Modify that removes a value from the indexed attribute
+ #
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self.l, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ msg["notUnique"] = ldb.MessageElement(
+ [aa_gt_max, bb_gt_max],
+ ldb.FLAG_MOD_REPLACE,
+ "notUnique")
+ self.l.modify(msg)
+
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ #
+ # Modify that does a constrained delete the indexed attribute
+ #
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ msg["notUnique"] = ldb.MessageElement(
+ [ab_gt_max],
+ ldb.FLAG_MOD_DELETE,
+ "notUnique")
+ self.l.modify(msg)
+
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ #
+ # Modify that does an unconstrained delete the indexed attribute
+ #
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ msg["notUnique"] = ldb.MessageElement(
+ [],
+ ldb.FLAG_MOD_DELETE,
+ "notUnique")
+ self.l.modify(msg)
+
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ None)
+
+ #
+ # Modify that adds a value to the indexed attribute
+ #
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ msg["notUnique"] = ldb.MessageElement(
+ [cc_gt_max],
+ ldb.FLAG_MOD_ADD,
+ "notUnique")
+ self.l.modify(msg)
+
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ #
+ # Modify that adds a values to the indexed attribute
+ #
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(self.l, "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ msg["notUnique"] = ldb.MessageElement(
+ [aa_gt_max, ab_gt_max],
+ ldb.FLAG_MOD_ADD,
+ "notUnique")
+ self.l.modify(msg)
+
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
+ #
+ # Test Sub tree searches when checkBaseOnSearch is enabled and the
+ # DN indexes are truncated and collide.
+ #
+ def test_check_base_on_search_truncated_dn_keys(self):
+ #
+ # Except for the base DN's
+ # all entries the DN index key gets truncated to
+ # 0 1 2 3 4 5
+ # 12345678901234567890123456789012345678901234567890
+ # @INDEX:@IDXDN:OU=??,OU=CHECK_BASE_DN_XXXX,DC=SAMBA
+ # The base DN-s truncate to
+ # @INDEX:@IDXDN:OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR
+ #
+ checkbaseonsearch = {"dn": "@OPTIONS",
+ "checkBaseOnSearch": b"TRUE"}
+ self.l.add(checkbaseonsearch)
+
+ self.l.add({"dn": "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcdef"})
+ self.l.add({"dn": "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcdee"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR",
+ b"0123456789abcdee" + b"0123456789abcdef")
+
+ self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcdec"})
+ self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcdeb"})
+ self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3",
+ "objectUUID": b"0123456789abcded"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA",
+ b"0123456789abcdeb" + b"0123456789abcdec" + b"0123456789abcded")
+
+ self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
+ "objectUUID": b"0123456789abcde0"})
+ self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3",
+ "objectUUID": b"0123456789abcde2"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcde1" + b"0123456789abcde2")
+
+ res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
+ scope=ldb.SCOPE_SUBTREE)
+ self.assertEqual(len(res), 3)
+ self.assertTrue(
+ contains(res, "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1"))
+
+ res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2",
+ scope=ldb.SCOPE_SUBTREE)
+ self.assertEqual(len(res), 3)
+ self.assertTrue(
+ contains(res, "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2"))
+ self.assertTrue(
+ contains(res, "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2"))
+
+ try:
+ res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3",
+ scope=ldb.SCOPE_SUBTREE)
+ self.fail("Expected exception no thrown")
+ except ldb.LdbError as e:
+ code = e.args[0]
+ self.assertEqual(ldb.ERR_NO_SUCH_OBJECT, code)
+
+
+# Run the index truncation tests against an lmdb backend
+class MaxIndexKeyLengthTestsLmdb(MaxIndexKeyLengthTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(MaxIndexKeyLengthTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(MaxIndexKeyLengthTestsLmdb, self).tearDown()
+
+
+class OrderedIntegerRangeTests(LdbBaseTest):
+
+ def tearDown(self):
+ shutil.rmtree(self.testdir)
+ super(OrderedIntegerRangeTests, self).tearDown()
+
+ # Ensure the LDB is closed now, so we close the FD
+ del(self.l)
+
+ def setUp(self):
+ super(OrderedIntegerRangeTests, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, "ordered_integer_test.ldb")
+
+ self.l = ldb.Ldb(self.url(),
+ options=self.options())
+ self.l.add({"dn": "@ATTRIBUTES",
+ "int64attr": "ORDERED_INTEGER"})
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXATTR": [b"int64attr"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]})
+
+ def options(self):
+ if self.prefix == MDB_PREFIX:
+ return ['modules:rdn_name',
+ 'disable_full_db_scan_for_self_test:1']
+ else:
+ return ['modules:rdn_name']
+
+ def test_comparison_expression(self):
+ int64_max = 2**63-1
+ int64_min = -2**63
+ test_nums = list(range(-5, 5))
+ test_nums += list(range(int64_max-5, int64_max+1))
+ test_nums += list(range(int64_min, int64_min+5))
+ test_nums = sorted(test_nums)
+
+ for (i, num) in enumerate(test_nums):
+ ouuid = 0x0123456789abcdef + i
+ ouuid_s = bytes(('0' + hex(ouuid)[2:]).encode())
+ self.l.add({"dn": "OU=COMPTESTOU{},DC=SAMBA,DC=ORG".format(i),
+ "objectUUID": ouuid_s,
+ "int64attr": str(num)})
+
+ def assert_int64_expr(expr, py_expr=None):
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(int64attr%s)" % (expr))
+
+ if not py_expr:
+ py_expr = expr
+ expect = [n for n in test_nums if eval(str(n) + py_expr)]
+ vals = sorted([int(r.get("int64attr")[0]) for r in res])
+ self.assertEqual(len(res), len(expect))
+ self.assertEqual(set(vals), set(expect))
+ self.assertEqual(expect, vals)
+
+ assert_int64_expr(">=-2")
+ assert_int64_expr("<=2")
+ assert_int64_expr(">=" + str(int64_min))
+ assert_int64_expr("<=" + str(int64_min))
+ assert_int64_expr("<=" + str(int64_min+1))
+ assert_int64_expr("<=" + str(int64_max))
+ assert_int64_expr(">=" + str(int64_max))
+ assert_int64_expr(">=" + str(int64_max-1))
+ assert_int64_expr("=10", "==10")
+
+ def test_comparison_expression_duplicates(self):
+ int64_max = 2**63-1
+ int64_min = -2**63
+ test_nums = list(range(-5, 5)) * 3
+ test_nums += list(range(-20, 20, 5)) * 2
+ test_nums += list(range(-50, 50, 15))
+ test_nums = sorted(test_nums)
+
+ for (i, num) in enumerate(test_nums):
+ ouuid = 0x0123456789abcdef + i
+ ouuid_s = bytes(('0' + hex(ouuid)[2:]).encode())
+ self.l.add({"dn": "OU=COMPTESTOU{},DC=SAMBA,DC=ORG".format(i),
+ "objectUUID": ouuid_s,
+ "int64attr": str(num)})
+
+ def assert_int64_expr(expr, py_expr=None):
+ res = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(int64attr%s)" % (expr))
+
+ if not py_expr:
+ py_expr = expr
+ expect = [n for n in test_nums if eval(str(n) + py_expr)]
+ vals = sorted([int(r.get("int64attr")[0]) for r in res])
+ self.assertEqual(len(res), len(expect))
+ self.assertEqual(set(vals), set(expect))
+ self.assertEqual(expect, vals)
+
+ assert_int64_expr(">=-2")
+ assert_int64_expr("<=2")
+ assert_int64_expr(">=" + str(int64_min))
+ assert_int64_expr("<=" + str(int64_min))
+ assert_int64_expr("<=" + str(int64_min+1))
+ assert_int64_expr("<=" + str(int64_max))
+ assert_int64_expr(">=" + str(int64_max))
+ assert_int64_expr(">=" + str(int64_max-1))
+ assert_int64_expr("=-5", "==-5")
+ assert_int64_expr("=5", "==5")
+
+
+# Run the ordered integer range tests against an lmdb backend
+class OrderedIntegerRangeTestsLmdb(OrderedIntegerRangeTests):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(OrderedIntegerRangeTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(OrderedIntegerRangeTestsLmdb, self).tearDown()
+
+
+# Run the index truncation tests against an lmdb backend
+class RejectSubDBIndex(LdbBaseTest):
+
+ def setUp(self):
+ if os.environ.get('HAVE_LMDB', '1') == '0':
+ self.skipTest("No lmdb backend")
+ self.prefix = MDB_PREFIX
+ super(RejectSubDBIndex, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir,
+ "reject_subidx_test.ldb")
+ self.l = ldb.Ldb(self.url(),
+ options=[
+ "modules:rdn_name"])
+
+ def tearDown(self):
+ super(RejectSubDBIndex, self).tearDown()
+
+ def test_try_subdb_index(self):
+ try:
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDX_LMDB_SUBDB": [b"1"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"],
+ })
+ except ldb.LdbError as e:
+ code = e.args[0]
+ string = e.args[1]
+ self.assertEqual(ldb.ERR_OPERATIONS_ERROR, code)
+ self.assertIn("sub-database index", string)
+
+
+if __name__ == '__main__':
+ import unittest
+ unittest.TestProgram()
diff --git a/lib/ldb/tests/python/repack.py b/lib/ldb/tests/python/repack.py
new file mode 100644
index 0000000..0844cd2
--- /dev/null
+++ b/lib/ldb/tests/python/repack.py
@@ -0,0 +1,204 @@
+import os
+from unittest import TestCase
+import shutil
+from subprocess import check_output
+import ldb
+
+TDB_PREFIX = "tdb://"
+MDB_PREFIX = "mdb://"
+
+def tempdir():
+ import tempfile
+ try:
+ dir_prefix = os.path.join(os.environ["SELFTEST_PREFIX"], "tmp")
+ except KeyError:
+ dir_prefix = None
+ return tempfile.mkdtemp(dir=dir_prefix)
+
+
+# Check enabling and disabling GUID indexing works and that the database is
+# repacked at version 2 if GUID indexing is enabled, or version 1 if disabled.
+class GUIDIndexAndPackFormatTests(TestCase):
+ prefix = TDB_PREFIX
+
+ def setup_newdb(self):
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir,
+ "guidpackformattest.ldb")
+ url = self.prefix + self.filename
+ self.l = ldb.Ldb(url, options=["modules:"])
+
+ self.num_recs_added = 0
+
+ #guidindexpackv1.ldb is a pre-made database packed with version 1 format
+ #but with GUID indexing enabled, which is not allowed, so Samba should
+ #repack the database on the first transaction.
+ def setup_premade_v1_db(self):
+ db_name = "guidindexpackv1.ldb"
+ this_file_dir = os.path.dirname(os.path.abspath(__file__))
+ db_path = os.path.join(this_file_dir, "../", db_name)
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir, db_name)
+
+ shutil.copy(db_path, self.filename)
+
+ url = self.prefix + self.filename
+ self.l = ldb.Ldb(url, options=["modules:"])
+ self.num_recs_added = 10
+
+ def tearDown(self):
+ if hasattr(self, 'testdir'):
+ shutil.rmtree(self.testdir)
+
+ def add_one_rec(self):
+ ouuid = 0x0123456789abcdef + self.num_recs_added
+ ouuid_s = '0' + hex(ouuid)[2:]
+ dn = "OU=GUIDPFTEST{},DC=SAMBA,DC=ORG".format(self.num_recs_added)
+ rec = {"dn": dn, "objectUUID": ouuid_s, "distinguishedName": dn}
+ self.l.add(rec)
+ self.num_recs_added += 1
+
+ # Turn GUID back into a str for easier comparisons
+ return rec
+
+ def set_guid_indexing(self, enable=True):
+ modmsg = ldb.Message()
+ modmsg.dn = ldb.Dn(self.l, '@INDEXLIST')
+
+ attrs = {"@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
+ for attr, val in attrs.items():
+ replace = ldb.FLAG_MOD_REPLACE
+ el = val if enable else []
+ el = ldb.MessageElement(elements=el, flags=replace, name=attr)
+ modmsg.add(el)
+
+ self.l.modify(modmsg)
+
+ # Parse out the comments above each record that ldbdump produces
+ # containing pack format version and KV level key for each record.
+ # Return all GUID index keys and the set of all unique pack formats.
+ def ldbdump_guid_keys_pack_formats(self):
+ dump = check_output(["bin/ldbdump", "-i", self.filename])
+ dump = dump.decode("utf-8")
+ dump = dump.split("\n")
+
+ comments = [s for s in dump if s.startswith("#")]
+
+ guid_key_tag = "# key: GUID="
+ guid_keys = {c[len(guid_key_tag):] for c in comments
+ if c.startswith(guid_key_tag)}
+
+ pack_format_tag = "# pack format: "
+ pack_formats = {c[len(pack_format_tag):] for c in comments
+ if c.startswith(pack_format_tag)}
+ pack_formats = [int(s, 16) for s in pack_formats]
+
+ return guid_keys, pack_formats
+
+ # Put the whole database in a dict so we can easily check the database
+ # hasn't changed
+ def get_database(self):
+ recs = self.l.search(base="", scope=ldb.SCOPE_SUBTREE, expression="")
+ db = dict()
+ for r in recs:
+ dn = str(r.dn)
+ self.assertNotIn(dn, db)
+ db[dn] = dict()
+ for k in r.keys():
+ k = str(k)
+ db[dn][k] = str(r.get(k))
+ return db
+
+ # Toggle GUID indexing on and off a few times, and check that when GUID
+ # indexing is enabled, the database is repacked to pack format V2, and
+ # when GUID indexing is disabled again, the database is repacked with
+ # pack format V1.
+ def toggle_guidindex_check_pack(self):
+ expect_db = self.get_database()
+
+ for enable in [False, False, True, False, True, True, False]:
+ pf = ldb.PACKING_FORMAT_V2 if enable else ldb.PACKING_FORMAT
+
+ self.set_guid_indexing(enable=enable)
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ num_guid_keys = self.num_recs_added if enable else 0
+ self.assertEqual(len(guid_keys), num_guid_keys)
+ self.assertEqual(pack_formats, [pf])
+ self.assertEqual(self.get_database(), expect_db)
+
+ rec = self.add_one_rec()
+ expect_db[rec['dn']] = rec
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ num_guid_keys = self.num_recs_added if enable else 0
+ self.assertEqual(len(guid_keys), num_guid_keys)
+ self.assertEqual(pack_formats, [pf])
+ self.assertEqual(self.get_database(), expect_db)
+
+ # Check a newly created database is initially packed at V1, then is
+ # repacked at V2 when GUID indexing is enabled.
+ def test_repack(self):
+ self.setup_newdb()
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ self.assertEqual(len(guid_keys), 0)
+ self.assertEqual(pack_formats, [ldb.PACKING_FORMAT])
+ self.assertEqual(self.get_database(), {})
+
+ self.l.add({"dn": "@ATTRIBUTES"})
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ self.assertEqual(len(guid_keys), 0)
+ self.assertEqual(pack_formats, [ldb.PACKING_FORMAT])
+ self.assertEqual(self.get_database(), {})
+
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]})
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ self.assertEqual(len(guid_keys), 0)
+ self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2])
+ self.assertEqual(self.get_database(), {})
+
+ rec = self.add_one_rec()
+ expect_db = {rec["dn"]: rec}
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ self.assertEqual(len(guid_keys), 1)
+ self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2])
+ self.assertEqual(self.get_database(), expect_db)
+
+ self.toggle_guidindex_check_pack()
+
+ # Check a database with V1 format with GUID indexing enabled is repacked
+ # with version 2 format.
+ def test_guid_indexed_v1_db(self):
+ self.setup_premade_v1_db()
+
+ expect_db = self.get_database()
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ self.assertEqual(len(guid_keys), self.num_recs_added)
+ self.assertEqual(pack_formats, [ldb.PACKING_FORMAT])
+ self.assertEqual(self.get_database(), expect_db)
+
+ rec = self.add_one_rec()
+ expect_db[rec['dn']] = rec
+
+ guid_keys, pack_formats = self.ldbdump_guid_keys_pack_formats()
+ self.assertEqual(len(guid_keys), self.num_recs_added)
+ self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2])
+ self.assertEqual(self.get_database(), expect_db)
+
+ self.toggle_guidindex_check_pack()
+
+
+if __name__ == '__main__':
+ import unittest
+
+
+ unittest.TestProgram()
diff --git a/lib/ldb/tests/samba4.png b/lib/ldb/tests/samba4.png
new file mode 100644
index 0000000..c809688
--- /dev/null
+++ b/lib/ldb/tests/samba4.png
Binary files differ
diff --git a/lib/ldb/tests/sample_module.c b/lib/ldb/tests/sample_module.c
new file mode 100644
index 0000000..6ba9ed2
--- /dev/null
+++ b/lib/ldb/tests/sample_module.c
@@ -0,0 +1,122 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "ldb_module.h"
+
+static int sample_add_callback(struct ldb_request *down_req,
+ struct ldb_reply *ares)
+{
+ struct ldb_request *req =
+ talloc_get_type_abort(down_req->context,
+ struct ldb_request);
+
+ if (ares == NULL) {
+ return ldb_module_done(req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(req, ares->referral);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ return ldb_module_done(req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ return ldb_module_done(req, ares->controls,
+ ares->response, LDB_SUCCESS);
+}
+
+static int sample_add(struct ldb_module *mod, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(mod);
+ struct ldb_control *control = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_request *down_req = NULL;
+ int ret;
+
+ /* check if there's a relax control */
+ control = ldb_request_get_control(req, LDB_CONTROL_RELAX_OID);
+ if (control != NULL) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ msg = ldb_msg_copy_shallow(req, req->op.add.message);
+ if (msg == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_fmt(msg, "touchedBy", "sample");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_add_req(&down_req, ldb, req,
+ msg,
+ req->controls,
+ req, sample_add_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(down_req, msg);
+
+ /* go on with the call chain */
+ return ldb_next_request(mod, down_req);
+}
+
+static int sample_modify(struct ldb_module *mod, struct ldb_request *req)
+{
+ struct ldb_control *control;
+
+ /* check if there's a relax control */
+ control = ldb_request_get_control(req, LDB_CONTROL_RELAX_OID);
+ if (control != NULL) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* not found go on */
+ return ldb_next_request(mod, req);
+}
+
+
+static struct ldb_module_ops ldb_sample_module_ops = {
+ .name = "sample",
+ .add = sample_add,
+ .del = sample_modify,
+ .modify = sample_modify,
+};
+
+int ldb_sample_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_sample_module_ops);
+}
diff --git a/lib/ldb/tests/schema-tests/schema-add-test.ldif b/lib/ldb/tests/schema-tests/schema-add-test.ldif
new file mode 100644
index 0000000..472ab48
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema-add-test.ldif
@@ -0,0 +1,66 @@
+dn: CN=Users,DC=schema,DC=test
+objectClass: top
+objectClass: container
+cn: Users
+description: Default container for upgraded user accounts
+instanceType: 4
+whenCreated: 20050116175504.0Z
+whenChanged: 20050116175504.0Z
+uSNCreated: 1
+uSNChanged: 1
+showInAdvancedViewOnly: FALSE
+name: Users
+objectGUID: b847056a-9934-d87b-8a1a-99fabe0863c8
+systemFlags: 0x8c000000
+objectCategory: CN=Container,CN=Schema,CN=Configuration,DC=schema,DC=test
+isCriticalSystemObject: TRUE
+nTSecurityDescriptor: foo
+
+dn: CN=Administrator,CN=Users,DC=schema,DC=test
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: Administrator
+description: Built-in account for administering the computer/domain
+instanceType: 4
+whenCreated: 20050116175504.0Z
+whenChanged: 20050116175504.0Z
+uSNCreated: 1
+memberOf: CN=Group Policy Creator Owners,CN=Users,DC=schema,DC=test
+memberOf: CN=Domain Admins,CN=Users,DC=schema,DC=test
+memberOf: CN=Enterprise Admins,CN=Users,DC=schema,DC=test
+memberOf: CN=Schema Admins,CN=Users,DC=schema,DC=test
+memberOf: CN=Administrators,CN=Builtin,DC=schema,DC=test
+uSNChanged: 1
+name: Administrator
+objectGUID: 6c02f98c-46c6-aa38-5f13-a510cac04e6c
+userAccountControl: 0x10200
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 0
+primaryGroupID: 513
+objectSid: S-1-5-21-43662522-77495566-38969261-500
+adminCount: 1
+accountExpires: 9223372036854775807
+logonCount: 0
+sAMAccountName: Administrator
+sAMAccountType: 0x30000000
+objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=schema,DC=test
+isCriticalSystemObject: TRUE
+unicodePwd: samba
+nTSecurityDescriptor: foo
+
+dn: CN=Test,CN=Users,DC=schema,DC=test
+objectClass: top
+objectClass: test
+cn: Test
+description: This is a test
+objectCategory: CN=Test,CN=Schema,CN=Configuration,DC=schema,DC=test
+nTSecurityDescriptor: foo
+instanceType: 4
+
diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-1.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-1.ldif
new file mode 100644
index 0000000..b976724
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema-mod-test-1.ldif
@@ -0,0 +1,5 @@
+dn: CN=Test,CN=Users,DC=schema,DC=test
+changetype: modify
+replace: description
+description: this test must not fail
+
diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-2.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-2.ldif
new file mode 100644
index 0000000..fa193af
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema-mod-test-2.ldif
@@ -0,0 +1,5 @@
+dn: CN=Test,CN=Users,DC=schema,DC=test
+changetype: modify
+delete: description
+# this test must not fail
+
diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-3.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-3.ldif
new file mode 100644
index 0000000..8ab7798
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema-mod-test-3.ldif
@@ -0,0 +1,5 @@
+dn: CN=Test,CN=Users,DC=schema,DC=test
+changetype: modify
+add: description
+description: this test must not fail
+
diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-4.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-4.ldif
new file mode 100644
index 0000000..cbf0e60
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema-mod-test-4.ldif
@@ -0,0 +1,5 @@
+dn: CN=Test,CN=Users,DC=schema,DC=test
+changetype: modify
+add: foo
+foo: this test must fail
+
diff --git a/lib/ldb/tests/schema-tests/schema-mod-test-5.ldif b/lib/ldb/tests/schema-tests/schema-mod-test-5.ldif
new file mode 100644
index 0000000..bc64e9e
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema-mod-test-5.ldif
@@ -0,0 +1,5 @@
+dn: CN=Test,CN=Users,DC=schema,DC=test
+changetype: modify
+delete: nTSecurityDescriptor
+# this test must fail
+
diff --git a/lib/ldb/tests/schema-tests/schema.ldif b/lib/ldb/tests/schema-tests/schema.ldif
new file mode 100644
index 0000000..4ab1932
--- /dev/null
+++ b/lib/ldb/tests/schema-tests/schema.ldif
@@ -0,0 +1,100 @@
+dn: @INDEXLIST
+@IDXATTR: name
+@IDXATTR: sAMAccountName
+@IDXATTR: objectSid
+@IDXATTR: objectClass
+@IDXATTR: member
+@IDXATTR: uidNumber
+@IDXATTR: gidNumber
+@IDXATTR: unixName
+@IDXATTR: privilege
+@IDXATTR: lDAPDisplayName
+
+dn: @ATTRIBUTES
+realm: CASE_INSENSITIVE
+userPrincipalName: CASE_INSENSITIVE
+servicePrincipalName: CASE_INSENSITIVE
+name: CASE_INSENSITIVE
+dn: CASE_INSENSITIVE
+sAMAccountName: CASE_INSENSITIVE
+objectClass: CASE_INSENSITIVE
+unicodePwd: HIDDEN
+ntPwdHash: HIDDEN
+ntPwdHistory: HIDDEN
+lmPwdHash: HIDDEN
+lmPwdHistory: HIDDEN
+createTimestamp: HIDDEN
+modifyTimestamp: HIDDEN
+
+dn: @MODULES
+@LIST: timestamps,schema
+
+dn: CN=Top,CN=Schema,CN=Configuration,DC=schema,DC=test
+objectClass: top
+objectClass: classSchema
+lDAPDisplayName: top
+cn: Top
+uSNCreated: 1
+uSNChanged: 1
+subClassOf: top
+systemMustContain: objectClass
+systemMayContain: structuralObjectClass
+systemMayContain: createTimeStamp
+systemMayContain: modifyTimeStamp
+systemMayContain: creatorsName
+systemMayContain: modifiersName
+systemMayContain: hasSubordinates
+systemMayContain: subschemaSubentry
+systemMayContain: collectiveSubentry
+systemMayContain: entryUUID
+systemMayContain: entryCSN
+systemMayContain: namingCSN
+systemMayContain: superiorUUID
+systemMayContain: contextCSN
+systemMayContain: whenCreated
+systemMayContain: whenChanged
+systemMayContain: uSNCreated
+systemMayContain: uSNChanged
+systemMayContain: distinguishedName
+systemMayContain: name
+systemMayContain: cn
+systemMayContain: userPassword
+systemMayContain: labeledURI
+
+dn: CN=Class-Schema,CN=Schema,CN=Configuration,DC=schema,DC=test
+objectClass: top
+objectClass: classSchema
+lDAPDisplayName: classSchema
+cn: Class-Schema
+uSNCreated: 2
+uSNChanged: 2
+lDAPDisplayName: classSchema
+subClassOf: top
+systemMustContain: cn
+systemMustContain: subClassOf
+systemMayContain: systemPossSuperiors
+systemMayContain: systemOnly
+systemMayContain: systemMustContain
+systemMayContain: systemMayContain
+systemMayContain: systemAuxiliaryClass
+systemMayContain: possSuperiors
+systemMayContain: mustContain
+systemMayContain: mayContain
+systemMayContain: lDAPDisplayName
+systemMayContain: auxiliaryClass
+
+dn: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=schema,DC=test
+objectClass: top
+objectClass: classSchema
+cn: Attribute-Schema
+uSNCreated: 3
+uSNChanged: 3
+lDAPDisplayName: attributeSchema
+subClassOf: top
+systemMustContain: oMSyntax
+systemMustContain: lDAPDisplayName
+systemMustContain: isSingleValued
+systemMustContain: cn
+systemMustContain: attributeSyntax
+systemMustContain: attributeID
+
diff --git a/lib/ldb/tests/slapd.conf b/lib/ldb/tests/slapd.conf
new file mode 100644
index 0000000..fa2789d
--- /dev/null
+++ b/lib/ldb/tests/slapd.conf
@@ -0,0 +1,26 @@
+loglevel 0
+
+include tests/schema/core.schema
+include tests/schema/cosine.schema
+include tests/schema/inetorgperson.schema
+include tests/schema/openldap.schema
+include tests/schema/nis.schema
+
+
+pidfile tests/tmp/slapd.pid
+argsfile tests/tmp/slapd.args
+
+access to * by * write
+
+allow update_anon bind_anon_dn
+
+include tests/tmp/modules.conf
+
+defaultsearchbase "o=University of Michigan,c=TEST"
+
+backend bdb
+database bdb
+suffix "o=University of Michigan,c=TEST"
+directory tests/tmp/db
+index objectClass eq
+index uid eq
diff --git a/lib/ldb/tests/start_slapd.sh b/lib/ldb/tests/start_slapd.sh
new file mode 100755
index 0000000..4a4a35e
--- /dev/null
+++ b/lib/ldb/tests/start_slapd.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+mkdir -p $LDBDIR/tests/tmp/db
+
+# running slapd in the background (with &) means it stays in the same process group, so it can be
+# killed by timelimit
+slapd -d0 -f $LDBDIR/tests/slapd.conf -h "$($LDBDIR/tests/ldapi_url.sh)" $* &
+
+sleep 2
diff --git a/lib/ldb/tests/test-attribs.ldif b/lib/ldb/tests/test-attribs.ldif
new file mode 100644
index 0000000..79508c4
--- /dev/null
+++ b/lib/ldb/tests/test-attribs.ldif
@@ -0,0 +1,6 @@
+dn: @ATTRIBUTES
+uid: CASE_INSENSITIVE
+cn: CASE_INSENSITIVE
+ou: CASE_INSENSITIVE
+dn: CASE_INSENSITIVE
+
diff --git a/lib/ldb/tests/test-config.ldif b/lib/ldb/tests/test-config.ldif
new file mode 100644
index 0000000..7926a9e
--- /dev/null
+++ b/lib/ldb/tests/test-config.ldif
@@ -0,0 +1,67 @@
+##############################
+# global configuration options
+dn: cn=Global,cn=Config,cn=Samba
+objectclass: globalconfig
+LocalConfigCn: cn=%U,cn=Config,cn=Samba
+LocalConfigCn;1: cn=%U,cn=Config,cn=Samba
+LocalConfigCn;2: cn=%I,cn=Config,cn=Samba
+LocalConfigCn;3: cn=%M,cn=Config,cn=Samba
+
+#############
+dn: cn=Protocol,cn=Global,cn=Config,cn=Samba
+maxXmit: 7000
+
+################################
+dn: cn=Volker,cn=Config,cn=Samba
+Workgroup: VNET3
+UnixCharset: UTF8
+Security: user
+Interfaces: vmnet* eth*
+NetbiosName: blu
+GuestAccount: tridge
+
+#################################
+dn: cn=Volker,cn=Config,cn=Samba
+Workgroup: VNET3
+UnixCharset: UTF8
+Security: user
+Interfaces: vmnet* eth*
+NetbiosName: blu
+GuestAccount: tridge
+Include: cn=%U,cn=MyConfig,cn=Config,cn=Samba
+
+#### ((objectClass=fileshare)(cn=test))
+##############################
+# [test] share
+dn: cn=test,cn=Shares,cn=Config,cn=Samba
+objectclass: fileshare
+cn: test
+Comment: a test share
+Path: /home/tridge/samba4/prefix/test
+ReadOnly: no
+
+#####################################
+# [msdn] a remote proxy share, stored
+# on \\msdn\test
+dn: cn=msdn,cn=Shares,cn=Config,cn=Samba
+objectclass: fileshare
+cn: msdn
+NtvfsHandler: cifs
+ReadOnly: no
+_CifsServer: msdn
+_CifsUser: administrator
+_CifsPassword: penguin
+_CifsDomain: winxp
+_CifsShare: test
+
+
+##############################
+# [VisualC] share
+dn: cn=visualc,cn=Shares,cn=Config,cn=Samba
+objectclass: fileshare
+cn: VisualC
+Comment: VisualC development
+Path: /home/tridge/VisualC
+ReadOnly: no
+NtvfsHandler: simple
+
diff --git a/lib/ldb/tests/test-controls.sh b/lib/ldb/tests/test-controls.sh
new file mode 100755
index 0000000..328ed29
--- /dev/null
+++ b/lib/ldb/tests/test-controls.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+if [ -n "$TEST_DATA_PREFIX" ]; then
+ LDB_URL="$TEST_DATA_PREFIX/tdbtest.ldb"
+else
+ LDB_URL="tdbtest.ldb"
+fi
+export LDB_URL
+
+PATH=bin:$PATH
+export PATH
+
+rm -f $LDB_URL*
+
+echo "LDB_URL: $LDB_URL"
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: @MODULES
+@LIST: sample
+EOF
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: dc=bar
+dc: bar
+someThing: someThingElse
+EOF
+
+$VALGRIND ldbsearch "(touchedBy=sample)" | grep "touchedBy: sample" || exit 1
+# This action are expected to fails because the sample module return an error when presented the relax control
+
+cat <<EOF | $VALGRIND ldbadd --controls "relax:0" >/dev/null 2>&1 && exit 1
+dn: dc=foobar
+dc: foobar
+someThing: someThingElse
+EOF
+
+cat <<EOF | $VALGRIND ldbmodify --controls "relax:0" >/dev/null 2>&1 && exit 1
+dn: dc=bar
+changetype: modify
+replace someThing
+someThing: someThingElseBetter
+EOF
+
+$VALGRIND ldbsearch --controls "bypassoperational:0" >/dev/null 2>&1 || exit 1
diff --git a/lib/ldb/tests/test-default-config.ldif b/lib/ldb/tests/test-default-config.ldif
new file mode 100644
index 0000000..87b7bcd
--- /dev/null
+++ b/lib/ldb/tests/test-default-config.ldif
@@ -0,0 +1,17 @@
+##############################
+# global configuration options
+dn: cn=Global,cn=DefaultConfig,cn=Samba
+objectclass: globalconfig
+Workgroup: WORKGROUP
+UnixCharset: UTF8
+Security: user
+NetbiosName: blu
+GuestAccount: nobody
+
+##############################
+# [_default_] share
+dn: cn=_default_,cn=Shares,cn=DefaultConfig,cn=Samba
+objectclass: fileshare
+cn: _default_
+Path: /tmp
+ReadOnly: yes
diff --git a/lib/ldb/tests/test-dup-2.ldif b/lib/ldb/tests/test-dup-2.ldif
new file mode 100644
index 0000000..a426101
--- /dev/null
+++ b/lib/ldb/tests/test-dup-2.ldif
@@ -0,0 +1,6 @@
+dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Sentinel
+sn: USER
+uid: USER, Sentinel
+
diff --git a/lib/ldb/tests/test-dup.ldif b/lib/ldb/tests/test-dup.ldif
new file mode 100644
index 0000000..b35420b
--- /dev/null
+++ b/lib/ldb/tests/test-dup.ldif
@@ -0,0 +1,13 @@
+dn: cn=Fred Bassett,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Fred Bassett
+sn: Bassett
+uid: Bassett, Fred
+
+dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Sentinel
+sn: USER
+uid: USER, Sentinel
+
+
diff --git a/lib/ldb/tests/test-extended.sh b/lib/ldb/tests/test-extended.sh
new file mode 100755
index 0000000..f4dabd6
--- /dev/null
+++ b/lib/ldb/tests/test-extended.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+echo "Running extended search tests"
+
+mv $LDB_URL $LDB_URL.1
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: cn=testrec1,cn=TEST
+i1: 1
+i2: 0
+i3: 1234
+i4: 0x7003004
+
+dn: cn=testrec2,cn=TEST
+i1: 0x800000
+
+dn: cn=testrec3,cn=TEST
+i1: 0x101010101
+i1: 7
+
+dn: cn=auser1,cn=TEST
+groupType: 2147483648
+samAccountType: 805306368
+
+dn: cn=auser2,cn=TEST
+groupType: 2147483648
+samAccountType: 805306369
+
+dn: cn=auser3,cn=TEST
+groupType: 2147483649
+samAccountType: 805306370
+
+dn: cn=auser4,cn=TEST
+groupType: 2147483649
+samAccountType: 805306369
+EOF
+
+checkcount()
+{
+ count=$1
+ expression="$2"
+ n=$($VALGRIND ldbsearch "$expression" | grep '^dn' | wc -l)
+ if [ $n != $count ]; then
+ echo "Got $n but expected $count for $expression"
+ $VALGRIND ldbsearch "$expression"
+ exit 1
+ fi
+ echo "OK: $count $expression"
+}
+
+checkcount 1 '(i3=1234)'
+checkcount 0 '(i3=12345)'
+
+checkcount 2 '(i1:1.2.840.113556.1.4.803:=1)'
+checkcount 1 '(i1:1.2.840.113556.1.4.803:=3)'
+checkcount 1 '(i1:1.2.840.113556.1.4.803:=7)'
+checkcount 0 '(i1:1.2.840.113556.1.4.803:=15)'
+checkcount 1 '(i1:1.2.840.113556.1.4.803:=0x800000)'
+checkcount 1 '(i1:1.2.840.113556.1.4.803:=8388608)'
+
+checkcount 2 '(i1:1.2.840.113556.1.4.804:=1)'
+checkcount 2 '(i1:1.2.840.113556.1.4.804:=3)'
+checkcount 2 '(i1:1.2.840.113556.1.4.804:=7)'
+checkcount 2 '(i1:1.2.840.113556.1.4.804:=15)'
+checkcount 1 '(i1:1.2.840.113556.1.4.804:=0x800000)'
+checkcount 1 '(i1:1.2.840.113556.1.4.804:=8388608)'
+
+# this is one that w2k gives
+checkcount 3 '(|(|(&(!(groupType:1.2.840.113556.1.4.803:=1))(groupType:1.2.840.113556.1.4.803:=2147483648)(groupType:1.2.840.113556.1.4.804:=10))(samAccountType=805306368))(samAccountType=805306369))'
diff --git a/lib/ldb/tests/test-generic.sh b/lib/ldb/tests/test-generic.sh
new file mode 100755
index 0000000..03ac914
--- /dev/null
+++ b/lib/ldb/tests/test-generic.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+
+if [ -z "$LDB_SPECIALS" ]; then
+ LDB_SPECIALS=1
+ export LDB_SPECIALS
+fi
+
+echo "LDB_URL: $LDB_URL"
+
+echo "Adding base elements"
+$VALGRIND ldbadd $LDBDIR/tests/test.ldif || exit 1
+
+echo "Adding again - should fail"
+$VALGRIND ldbadd $LDBDIR/tests/test.ldif 2>/dev/null && {
+ echo "Should have failed to add again - gave $?"
+ exit 1
+}
+
+echo "Adding LDIF with one already-existing user again - should fail"
+$VALGRIND ldbadd $LDBDIR/tests/test-dup.ldif 2>/dev/null && {
+ echo "Should have failed to add again - gave $?"
+ exit 1
+}
+
+echo "Adding again - should succeed (as previous failed)"
+$VALGRIND ldbadd $LDBDIR/tests/test-dup-2.ldif || exit 1
+
+echo "Modifying elements"
+$VALGRIND ldbmodify $LDBDIR/tests/test-modify.ldif || exit 1
+
+echo "Modify LDIF with one un-met constraint - should fail"
+$VALGRIND ldbadd $LDBDIR/tests/test-modify-unmet.ldif 2>/dev/null && {
+ echo "Should have failed to modify - gave $?"
+ exit 1
+}
+
+echo "Modify LDIF with after failure of un-met constraint - should also fail"
+$VALGRIND ldbadd $LDBDIR/tests/test-modify-unmet-2.ldif 2>/dev/null && {
+ echo "Should have failed to modify - gave $?"
+ exit 1
+}
+
+echo "Showing modified record"
+$VALGRIND ldbsearch '(uid=uham)' || exit 1
+
+echo "Rename entry with ldbmodify - modrdn"
+$VALGRIND ldbmodify $LDBDIR/tests/test-modify-modrdn.ldif || exit 1
+
+echo "Rename entry with ldbrename"
+OLDDN="cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST"
+NEWDN="cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST"
+$VALGRIND ldbrename "$OLDDN" "$NEWDN" || exit 1
+
+echo "Showing renamed record"
+$VALGRIND ldbsearch '(uid=uham)' || exit 1
+
+echo "Starting ldbtest"
+$VALGRIND ldbtest --num-records 100 --num-searches 10 || exit 1
+
+if [ $LDB_SPECIALS = 1 ]; then
+ echo "Adding index"
+ $VALGRIND ldbadd $LDBDIR/tests/test-index.ldif || exit 1
+fi
+
+echo "Adding bad attributes - should fail"
+$VALGRIND ldbadd $LDBDIR/tests/test-wrong_attributes.ldif && {
+ echo "Should fhave failed - gave $?"
+ exit 1
+}
+
+echo "Testing indexed search"
+$VALGRIND ldbsearch '(uid=uham)' || exit 1
+$VALGRIND ldbsearch '(&(objectclass=person)(objectclass=person)(objectclass=top))' || exit 1
+$VALGRIND ldbsearch '(&(uid=uham)(uid=uham))' || exit 1
+$VALGRIND ldbsearch '(|(uid=uham)(uid=uham))' || exit 1
+$VALGRIND ldbsearch '(|(uid=uham)(uid=uham)(objectclass=OpenLDAPperson))' || exit 1
+$VALGRIND ldbsearch '(&(uid=uham)(uid=uham)(!(objectclass=xxx)))' || exit 1
+$VALGRIND ldbsearch '(&(objectclass=person)(uid=uham)(!(uid=uhamxx)))' uid \* \+ dn || exit 1
+$VALGRIND ldbsearch '(&(uid=uham)(uid=uha*)(title=*))' uid || exit 1
+
+echo "Testing invalid search expression"
+$VALGRIND ldbsearch '(&(uid=uham)(title=foo\blah))' uid && exit 1
+
+# note that the "((" is treated as an attribute not an expression
+# this matches the openldap ldapsearch behaviour of looking for a '='
+# to see if the first argument is an expression or not
+$VALGRIND ldbsearch '((' uid || exit 1
+$VALGRIND ldbsearch '(objectclass=)' uid || exit 1
+$VALGRIND ldbsearch -b 'cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST' --scope=base "" sn || exit 1
+
+echo "Test wildcard match"
+$VALGRIND ldbadd $LDBDIR/tests/test-wildcard.ldif || exit 1
+$VALGRIND ldbsearch '(cn=test*multi)' || exit 1
+$VALGRIND ldbsearch '(cn=*test*multi*)' || exit 1
+$VALGRIND ldbsearch '(cn=*test_multi)' || exit 1
+$VALGRIND ldbsearch '(cn=test_multi*)' || exit 1
+$VALGRIND ldbsearch '(cn=test*multi*test*multi)' || exit 1
+$VALGRIND ldbsearch '(cn=test*multi*test*multi*multi_*)' || exit 1
+
+echo "Starting ldbtest indexed"
+$VALGRIND ldbtest --num-records 100 --num-searches 500 || exit 1
+
+echo "Testing one level search"
+count=$($VALGRIND ldbsearch -b 'ou=Groups,o=University of Michigan,c=TEST' --scope=one 'objectclass=*' none | grep '^dn' | wc -l)
+if [ $count != 3 ]; then
+ echo returned $count records - expected 3
+ exit 1
+fi
+
+echo "Testing binary file attribute value"
+$VALGRIND ldbmodify $LDBDIR/tests/photo.ldif || exit 1
+count=$($VALGRIND ldbsearch '(cn=Hampster Ursula)' jpegPhoto | grep '^dn' | wc -l)
+if [ $count != 1 ]; then
+ echo returned $count records - expected 1
+ exit 1
+fi
+
+echo "*TODO* Testing UTF8 upper lower case searches !!"
+
+echo "Testing compare"
+count=$($VALGRIND ldbsearch '(cn>=t)' cn | grep '^dn' | wc -l)
+if [ $count != 1 ]; then
+ # only "cn: test_multi_test_multi_test_multi" (comes after "t")
+ # upper-cased words come before "t" - hence excluded
+ echo returned $count records - expected 1
+ exit 1
+fi
+$VALGRIND ldbsearch '(cn>t)' cn && exit 1 # strictly greater should not work
+
+count=$($VALGRIND ldbsearch '(cn<=t)' cn | grep '^dn' | wc -l)
+if [ $count != 18 ]; then
+ # everything except "cn: test_multi_test_multi_test_multi" (comes after "t")
+ # upper-cased letters come before "t" - hence included
+ echo returned $count records - expected 18
+ exit 1
+fi
+$VALGRIND ldbsearch '(cn<t)' cn && exit 1 # strictly less should not work
+
+checkcount()
+{
+ count=$1
+ scope=$2
+ basedn=$3
+ expression="$4"
+ n=$($VALGRIND ldbsearch --scope="$scope" -b "$basedn" "$expression" | grep '^dn' | wc -l)
+ if [ $n != $count ]; then
+ echo "Got $n but expected $count for $expression"
+ exit 1
+ fi
+ echo "OK: $count $expression"
+}
+
+checkcount 0 'base' '' '(uid=uham)'
+checkcount 0 'one' '' '(uid=uham)'
+
+checkcount 1 'base' 'cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST' '(uid=uham)'
+checkcount 1 'one' 'ou=Alumni Association,ou=People,o=University of Michigan,c=TEST' '(uid=uham)'
+checkcount 1 'one' 'ou=People,o=University of Michigan,c=TEST' '(ou=ldb test)'
diff --git a/lib/ldb/tests/test-index.ldif b/lib/ldb/tests/test-index.ldif
new file mode 100644
index 0000000..2681736
--- /dev/null
+++ b/lib/ldb/tests/test-index.ldif
@@ -0,0 +1,7 @@
+dn: @INDEXLIST
+@IDXATTR: uid
+@IDXATTR: objectclass
+
+dn: @ATTRIBUTES
+uid: CASE_INSENSITIVE
+
diff --git a/lib/ldb/tests/test-ldap.sh b/lib/ldb/tests/test-ldap.sh
new file mode 100755
index 0000000..6e0e4a4
--- /dev/null
+++ b/lib/ldb/tests/test-ldap.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+PATH=/usr/local/sbin:/usr/sbin:/sbin:$PATH
+export PATH
+SCHEMA_NEEDED="core nis cosine inetorgperson openldap"
+
+# setup needed schema files
+for f in $SCHEMA_NEEDED; do
+ if [ ! -r tests/schema/$f.schema ]; then
+ mkdir -p tests/schema
+ if [ -r /etc/ldap/schema/$f.schema ]; then
+ ln -s /etc/ldap/schema/$f.schema tests/schema/$f.schema
+ continue
+ fi
+ if [ -r /etc/openldap/schema/$f.schema ]; then
+ ln -s /etc/openldap/schema/$f.schema tests/schema/$f.schema
+ continue
+ fi
+
+ echo "SKIPPING TESTS: you need the following OpenLDAP schema files"
+ for f in $SCHEMA_NEEDED; do
+ echo " $f.schema"
+ done
+ exit 0
+ fi
+done
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+LDB_URL=$($LDBDIR/tests/ldapi_url.sh)
+export LDB_URL
+
+PATH=bin:$PATH
+export PATH
+
+LDB_SPECIALS=0
+export LDB_SPECIALS
+
+if $LDBDIR/tests/init_slapd.sh &&
+ $LDBDIR/tests/start_slapd.sh &&
+ $LDBDIR/tests/test-generic.sh; then
+ echo "ldap tests passed"
+ ret=0
+else
+ echo "ldap tests failed"
+ ret=$?
+fi
+
+#$LDBDIR/tests/kill_slapd.sh
+
+exit $ret
diff --git a/lib/ldb/tests/test-modify-modrdn.ldif b/lib/ldb/tests/test-modify-modrdn.ldif
new file mode 100644
index 0000000..efa3149
--- /dev/null
+++ b/lib/ldb/tests/test-modify-modrdn.ldif
@@ -0,0 +1,12 @@
+dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+changetype: moddn
+newrdn: cn=Hampster Ursula
+deleteoldrdn: 1
+newsuperior: ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+
+dn: cn=Hampster Ursula,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+changetype: modrdn
+newrdn: cn=Ursula Hampster
+deleteoldrdn: 1
diff --git a/lib/ldb/tests/test-modify-unmet-2.ldif b/lib/ldb/tests/test-modify-unmet-2.ldif
new file mode 100644
index 0000000..8760938
--- /dev/null
+++ b/lib/ldb/tests/test-modify-unmet-2.ldif
@@ -0,0 +1,7 @@
+dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+changetype: modify
+delete: drink
+drink: water
+
+
diff --git a/lib/ldb/tests/test-modify-unmet.ldif b/lib/ldb/tests/test-modify-unmet.ldif
new file mode 100644
index 0000000..6a46cdf
--- /dev/null
+++ b/lib/ldb/tests/test-modify-unmet.ldif
@@ -0,0 +1,15 @@
+dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+changetype: modify
+add: drink
+drink: water
+
+dn: cn=Sentinel,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+changetype: modify
+add: sn
+sn: TEST
+-
+delete: sn
+sn: USER2
+
diff --git a/lib/ldb/tests/test-modify.ldif b/lib/ldb/tests/test-modify.ldif
new file mode 100644
index 0000000..e5b9ca4
--- /dev/null
+++ b/lib/ldb/tests/test-modify.ldif
@@ -0,0 +1,23 @@
+dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+changetype: modify
+add: drink
+drink: mango lassi
+-
+add: drink
+drink: lemonade
+-
+delete: pager
+-
+replace: telephonenumber
+telephonenumber: +61 2 6260 6012
+telephonenumber: +61 412 666 929
+-
+delete: telephonenumber
+telephonenumber: +61 2 6260 6012
+-
+delete: telephonenumber
+telephonenumber: +61 412 666 929
+-
+add: telephonenumber
+telephonenumber: +61 412 666 929
diff --git a/lib/ldb/tests/test-schema.sh b/lib/ldb/tests/test-schema.sh
new file mode 100755
index 0000000..15582a2
--- /dev/null
+++ b/lib/ldb/tests/test-schema.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+LDB_URL="tdb://schema.ldb"
+export LDB_URL
+
+rm -f schema.ldb
+
+echo "LDB_URL: $LDB_URL"
+
+echo "Adding schema"
+$VALGRIND bin/ldbadd $LDBDIR/tests/schema-tests/schema.ldif || exit 1
+
+echo "Adding few test elements (no failure expected here)"
+$VALGRIND bin/ldbadd $LDBDIR/tests/schema-tests/schema-add-test.ldif || exit 1
+
+echo "Modifying elements (2 failures expected here)"
+
+$VALGRIND bin/ldbmodify $LDBDIR/tests/schema-tests/schema-mod-test-1.ldif || exit 1
+$VALGRIND bin/ldbmodify $LDBDIR/tests/schema-tests/schema-mod-test-2.ldif || exit 1
+$VALGRIND bin/ldbmodify $LDBDIR/tests/schema-tests/schema-mod-test-3.ldif || exit 1
+$VALGRIND bin/ldbmodify $LDBDIR/tests/schema-tests/schema-mod-test-4.ldif
+if [ "$?" = "0" ]; then
+ echo "test failed!"
+ exit 1
+fi
+$VALGRIND bin/ldbmodify $LDBDIR/tests/schema-tests/schema-mod-test-5.ldif
+if [ "$?" = "0" ]; then
+ echo "test failed!"
+ exit 1
+fi
+
+echo "Showing modified record"
+$VALGRIND bin/ldbsearch '(cn=Test)' || exit 1
diff --git a/lib/ldb/tests/test-soloading.sh b/lib/ldb/tests/test-soloading.sh
new file mode 100755
index 0000000..a783305
--- /dev/null
+++ b/lib/ldb/tests/test-soloading.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+if [ -n "$TEST_DATA_PREFIX" ]; then
+ LDB_URL="$TEST_DATA_PREFIX/tdbtest.ldb"
+else
+ LDB_URL="tdbtest.ldb"
+fi
+export LDB_URL
+
+PATH=bin:$PATH
+export PATH
+
+rm -f $LDB_URL*
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: @MODULES
+@LIST: sample
+EOF
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: dc=bar
+dc: bar
+someThing: someThingElse
+EOF
+
+$VALGRIND ldbsearch "(touchedBy=sample)" | grep "touchedBy: sample" || exit 1
diff --git a/lib/ldb/tests/test-sqlite3.sh b/lib/ldb/tests/test-sqlite3.sh
new file mode 100755
index 0000000..389132d
--- /dev/null
+++ b/lib/ldb/tests/test-sqlite3.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+LDB_URL="sqlite3://sqltest.ldb"
+export LDB_URL
+
+rm -f sqltest.ldb
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+PATH=bin:$PATH
+export PATH
+
+LDB_SPECIALS=0
+export LDB_SPECIALS
+
+$LDBDIR/tests/test-generic.sh
+
+#. $LDBDIR/tests/test-extended.sh
+
+#. $LDBDIR/tests/test-tdb-features.sh
diff --git a/lib/ldb/tests/test-tdb-features.sh b/lib/ldb/tests/test-tdb-features.sh
new file mode 100644
index 0000000..1007fa0
--- /dev/null
+++ b/lib/ldb/tests/test-tdb-features.sh
@@ -0,0 +1,179 @@
+#!/bin/sh
+
+echo "Running tdb feature tests"
+
+mv $LDB_URL $LDB_URL.2
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: @MODULES
+@LIST: rdn_name
+EOF
+
+checkcount()
+{
+ count=$1
+ expression="$2"
+ n=$($VALGRIND ldbsearch "$expression" | grep '^dn' | wc -l)
+ if [ $n != $count ]; then
+ echo "Got $n but expected $count for $expression"
+ $VALGRIND ldbsearch "$expression"
+ exit 1
+ fi
+ echo "OK: $count $expression"
+}
+
+echo "Testing case sensitive search"
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: cn=t1,cn=TEST
+objectClass: testclass
+test: foo
+EOF
+checkcount 1 '(test=foo)'
+checkcount 0 '(test=FOO)'
+checkcount 0 '(test=FO*)'
+checkcount 1 '(cn=t1)'
+
+echo "Making case insensitive"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: @ATTRIBUTES
+changetype: add
+add: test
+test: CASE_INSENSITIVE
+EOF
+
+echo $ldif | $VALGRIND ldbmodify || exit 1
+checkcount 1 '(test=foo)'
+checkcount 1 '(test=FOO)'
+checkcount 1 '(test=fo*)'
+
+echo "adding i"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: cn=t1,cn=TEST
+changetype: modify
+add: i
+i: 0x100
+EOF
+checkcount 1 '(i=0x100)'
+checkcount 0 '(i=256)'
+
+echo "marking i as INTEGER"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: @ATTRIBUTES
+changetype: modify
+add: i
+i: INTEGER
+EOF
+checkcount 1 '(i=0x100)'
+checkcount 1 '(i=256)'
+
+echo "adding j"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: cn=t1,cn=TEST
+changetype: modify
+add: j
+j: 0x100
+EOF
+checkcount 1 '(j=0x100)'
+checkcount 0 '(j=256)'
+
+echo "Adding wildcard attribute"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: @ATTRIBUTES
+changetype: modify
+add: *
+*: INTEGER
+EOF
+checkcount 1 '(j=0x100)'
+checkcount 1 '(j=256)'
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: num=1
+EOF
+
+echo "Testing search for attribute after change to use wildcard"
+checkcount 1 '(num=1)'
+
+echo "Testing class search"
+checkcount 0 '(objectClass=otherclass)'
+checkcount 1 '(objectClass=testclass)'
+
+echo "Adding index"
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: @INDEXLIST
+@IDXATTR: i
+@IDXATTR: test
+EOF
+checkcount 1 '(i=0x100)'
+checkcount 1 '(i=256)'
+checkcount 0 '(i=-256)'
+checkcount 1 '(test=foo)'
+checkcount 1 '(test=FOO)'
+checkcount 1 '(test=*f*o)'
+
+echo "making test case sensitive"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: @ATTRIBUTES
+changetype: modify
+replace: test
+test: NONE
+EOF
+checkcount 1 '(test=foo)'
+checkcount 0 '(test=FOO)'
+checkcount 1 '(test=f*o*)'
+
+checkone()
+{
+ count=$1
+ base="$2"
+ expression="$3"
+ n=$($VALGRIND ldbsearch --scope=one -b "$base" "$expression" | grep '^dn' | wc -l)
+ if [ $n != $count ]; then
+ echo "Got $n but expected $count for $expression"
+ $VALGRIND ldbsearch --scope=one -b "$base" "$expression"
+ exit 1
+ fi
+ echo "OK: $count $expression"
+}
+
+echo "Removing wildcard attribute"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: @ATTRIBUTES
+changetype: modify
+delete: *
+*: INTEGER
+EOF
+
+echo "Adding one level indexes"
+cat <<EOF | $VALGRIND ldbmodify || exit 1
+dn: @INDEXLIST
+changetype: modify
+add: @IDXONE
+@IDXONE: 1
+EOF
+
+echo "Testing one level indexed search"
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: cn=one,cn=t1,cn=TEST
+objectClass: oneclass
+cn: one
+test: one
+EOF
+checkone 1 "cn=t1,cn=TEST" '(test=one)'
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: cn=two,cn=t1,cn=TEST
+objectClass: oneclass
+cn: two
+test: one
+
+dn: cn=three,cn=t1,cn=TEST
+objectClass: oneclass
+cn: three
+test: one
+
+dn: cn=four,cn=three,cn=t1,cn=TEST
+objectClass: oneclass
+cn: four
+test: one
+EOF
+checkone 3 "cn=t1,cn=TEST" '(test=one)'
+checkone 1 "cn=t1,cn=TEST" '(cn=two)'
diff --git a/lib/ldb/tests/test-tdb-subunit.sh b/lib/ldb/tests/test-tdb-subunit.sh
new file mode 100755
index 0000000..a89c01c
--- /dev/null
+++ b/lib/ldb/tests/test-tdb-subunit.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+BINDIR=$1
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+
+testit "ldb" $(dirname $0)/test-tdb.sh $BINDIR
diff --git a/lib/ldb/tests/test-tdb.sh b/lib/ldb/tests/test-tdb.sh
new file mode 100755
index 0000000..0b5414d
--- /dev/null
+++ b/lib/ldb/tests/test-tdb.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+BINDIR=$1
+
+if [ -n "$TEST_DATA_PREFIX" ]; then
+ LDB_URL="$TEST_DATA_PREFIX/tdbtest.ldb"
+ PYDESTDIR="$TEST_DATA_PREFIX"
+else
+ LDB_URL="tdbtest.ldb"
+fi
+export LDB_URL
+
+PATH=$BINDIR:$PATH
+export PATH
+
+if [ -z "$LDBDIR" ]; then
+ LDBDIR=$(dirname $0)/..
+ export LDBDIR
+fi
+
+cd $LDBDIR
+
+rm -f $LDB_URL*
+
+cat <<EOF | $VALGRIND ldbadd || exit 1
+dn: @MODULES
+@LIST: rdn_name
+EOF
+
+$VALGRIND ldbadd $LDBDIR/tests/init.ldif || exit 1
+
+. $LDBDIR/tests/test-generic.sh
+
+. $LDBDIR/tests/test-extended.sh
+
+. $LDBDIR/tests/test-tdb-features.sh
+
+. $LDBDIR/tests/test-controls.sh
diff --git a/lib/ldb/tests/test-wildcard.ldif b/lib/ldb/tests/test-wildcard.ldif
new file mode 100644
index 0000000..222512e
--- /dev/null
+++ b/lib/ldb/tests/test-wildcard.ldif
@@ -0,0 +1,5 @@
+dn: cn=test_multi_test_multi_test_multi,o=University of Michigan,c=TEST
+objectclass: person
+cn: test_multi_test_multi_test_multi
+sn: multi_test
+description: test multi wildcards matching
diff --git a/lib/ldb/tests/test-wrong_attributes.ldif b/lib/ldb/tests/test-wrong_attributes.ldif
new file mode 100644
index 0000000..27f45f0
--- /dev/null
+++ b/lib/ldb/tests/test-wrong_attributes.ldif
@@ -0,0 +1,3 @@
+dn: @ATTRIBUTES
+uid: CASE_INTENSIVE
+
diff --git a/lib/ldb/tests/test.ldif b/lib/ldb/tests/test.ldif
new file mode 100644
index 0000000..b3f0eed
--- /dev/null
+++ b/lib/ldb/tests/test.ldif
@@ -0,0 +1,440 @@
+dn: ou=Groups,o=University of Michigan,c=TEST
+objectclass: organizationalUnit
+ou: Groups
+
+dn: ou=Information Technology Division,ou=People,o=University of Michigan,c=TEST
+objectclass: organizationalUnit
+ou: Information Technology Division
+description:: aMODwoPDgsKCw4PCgsOCwotFVlZQw4PCg8OCwoPDg8KCw4LCv0zDg8KDw4LCgsOD
+ woLDgsKKT8ODwoPDgsKDw4PCgsOCwqs6w4PCg8OCwoLDg8KCw4LCjUQkw4PCg8OCwoLDg8KCw4LCi
+ 01QUcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoLDg8KCw4LCik/Dg8KDw4
+ LCgsODwoLDgsKLRCQoZitEJMODwoPDgsKCw4PCgsOCwrfDg8KDw4LCg8ODwoLDgsKIw4PCg8OCwoP
+ Dg8KCw4LCgcODwoPDgsKDw4PCgsOCwqHDg8KDw4LCgsODwoLDgsKLRCQkZitEJMODwoPDgsKCw4PC
+ gsOCwrfDg8KDw4LCg8ODwoLDgsKQw4PCg8OCwoPDg8KCw4LCisODwoPDgsKCw4PCgsOCwotFUVZqU
+ MODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsKAw4PCg8OCwoLDg8KCw4LCik85dCTDg8KDw4
+ LCgsODwoLDgsKFQ8ODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4L
+ Cvzl0JMODwoPDgsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODwoPD
+ gsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKLRCTDg8KDw4LCgsODwoLDgsKDw4PCg8OCwoLDg8KCw
+ 4LCuMODwoPDgsKDw4PCgsOCwoR0Q8ODwoPDgsKCw4PCgsOCwoM9w4PCg8OCwoPDg8KCw4LChMODwo
+ PDgsKDw4PCgsOCwoFOdTrDg8KDw4LCg8ODwoLDgsKHw4PCg8OCwoPDg8KCw4LChMODwoPDgsKDw4P
+ CgsOCwoFOw4PCg8OCwoPDg8KCw4LCqMODwoPDgsKDw4PCgsOCwrtHw4PCg8OCwoLDg8KCw4LChcOD
+ woPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsK4dMODwoPDgsKDw4PCgsOCwqjDg8KDw4LCg8ODw
+ oLDgsKtR8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCiMODwo
+ PDgsKDw4PCgsOCwr9SfGrDg8KDw4LCgsODwoLDgsKLQGgxw4PCg8OCwoPDg8KCw4LCoWhQw4PCg8O
+ CwoPDg8KCw4LCv8ODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKKT8ODwoPDgsKCw4PCgsOC
+ wotEJDDDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCgHTDg8KDw4LCgsODwoLDgsKDw4PCg
+ 8OCwoPDg8KCw4LCuHXDg8KDw4LCgsODwoLDgsKLRCRqw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4
+ PCgsOCwojDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpPDg8K
+ Dw4LCg8ODwoLDgsKQXV9eW8ODwoPDgsKCw4PCgsOCwoPDg8KDw4LCg8ODwoLDgsKEw4PCg8OCwoPD
+ g8KCw4LCgsODwoPDgsKDw4PCgsOCwozDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODw
+ oPDgsKDw4PCgsOCwozDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgs
+ OCwoxWV8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKxw4PCg8OCwoLDg8KCw4LCi3wkw4P
+ Cg8OCwoLDg8KCw4LCjcODwoPDgsKCw4PCgsOCwofDg8KDw4LCg8ODwoLDgsKof8ODwoPDgsKDw4PC
+ gsOCwr/Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoLDg8KCw4LCg8ODwoPDgsKDw4PCgsOCwrh5w4PCg
+ 8OCwoLDg8KCw4LChzQzw4PCg8OCwoPDg8KCw4LCicODwoPDgsKCw4PCgsOCworDg8KDw4LCgsODwo
+ LDgsKIw4PCg8OCwoLDg8KCw4LCuDFBw4PCg8OCwoPDg8KCw4LCvyTDg8KDw4LCgsODwoLDgsKNdDF
+ Bw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwrhfXsODwoPD
+ gsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCwoLDg8KCw
+ 4LCi8ODwoPDgsKDw4PCgsOCwo7Dg8KDw4LCgsODwoLDgsKBw4PCg8OCwoPDg8KCw4LCv8ODwoPDgs
+ KCw4PCgsOCwoTDg8KDw4LCgsODwoLDgsKAdcODwoPDgsKDw4PCgsOCwqhtw4PCg8OCwoLDg8KCw4L
+ ChcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKEw4PCg8OCwoPDg8KCw4LCsMODwoPDgsKC
+ w4PCgsOCwrhfXsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCg8ODwoLDgsKow4PCg8OCwoLDg8KCw4LCt
+ sODwoPDgsKDw4PCgsOCwq7Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4
+ PCgsOCwoPDg8KDw4LCg8ODwoLDgsKoZsODwoPDgsKCw4PCgsOCwoPDg8KDw4LCg8ODwoLDgsK4w4P
+ Cg8OCwoLDg8KCw4LCh8ODwoPDgsKDw4PCgsOCwpUzw4PCg8OCwoPDg8KCw4LCicODwoPDgsKCw4PC
+ gsOCworDg8KDw4LCgsODwoLDgsKISDJBw4PCg8OCwoPDg8KCw4LCvyTDg8KDw4LCgsODwoLDgsKNN
+ DJBw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKOw4PCg8OCwo
+ PDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpDDg8KDw4LCg8ODwoLDgsKIw4PCg8OCwoLDg8KCw4LCi8O
+ DwoPDgsKDw4PCgsOCwojDg8KDw4LCg8ODwoLDgsKow4PCg8OCwoPDg8KCw4LCnEzDg8KDw4LCgsOD
+ woLDgsKLSEBmw4PCg8OCwoLDg8KCw4LCg3lwdSTDg8KDw4LCgsODwoLDgsKBw4PCg8OCwoPDg8KCw
+ 4LCv8ODwoPDgsKCw4PCgsOCwobDg8KDw4LCgsODwoLDgsKAw4PCg8OCwoLDg8KCw4LChMODwoPDgs
+ KCw4PCgsOCwp/Dg8KDw4LCgsODwoLDgsKBw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwoj
+ Dg8KDw4LCgsODwoLDgsKAw4PCg8OCwoLDg8KCw4LChMODwoPDgsKCw4PCgsOCwpPDg8KDw4LCgsOD
+ woLDgsKBw4PCg8OCwoPDg8KCw4LCv1rDg8KDw4LCgsODwoLDgsKAw4PCg8OCwoLDg8KCw4LChMODw
+ oPDgsKCw4PCgsOCwodqw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PCgsOCwoBqaMODwoPDgsKCw4
+ PCgsOCwpBQw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKDIMODwoPDgsKCw4PCgsOCwopPw4PCg8OCwoL
+ Dg8KCw4LChcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKOacODwoPDgsKCw4PCgsOCwrhf
+ XsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCw
+ oLDg8KCw4LCgcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKGw4PCg8OCwoLDg8KCw4LCgM
+ ODwoPDgsKCw4PCgsOCwoRJw4PCg8OCwoLDg8KCw4LCgcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsO
+ DwoLDgsKIw4PCg8OCwoLDg8KCw4LCgMODwoPDgsKCw4PCgsOCwoQ9w4PCg8OCwoLDg8KCw4LCgcOD
+ woPDgsKDw4PCgsOCwr9aw4PCg8OCwoLDg8KCw4LCgMODwoPDgsKCw4PCgsOCwoQxw4PCg8OCwoLDg
+ 8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwoM9w4PCg8OCwoPDg8KCw4LCm0
+ 7Dg8KDw4LCgsODwoLDgsKEw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsK
+ Cw4PCgsOCwrhfXsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLD
+ gsKCw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwrhfXsODw
+ oPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgs
+ OCwo7Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoLDg8KCw4LCkMODwoPDgsKDw4PCgsOCwojDg8KDw4L
+ CgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCiMODwoPDgsKDw4PCgsOCwqjDg8KDw4LCg8ODwoLDgsK+
+ S8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKww4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKDw
+ 4PCgsOCwoTDg8KDw4LCgsODwoLDgsKKT1DDg8KDw4LCg8ODwoLDgsKoRsODwoPDgsKCw4PCgsOCwo
+ vDg8KDw4LCg8ODwoLDgsK4w4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwrZ0Y8ODwoPDgsK
+ Cw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsK/dF/Dg8KDw4LCgsODwoLDgsKhdHpPw4PCg8OCwoLDg8KC
+ w4LCi8ODwoPDgsKDw4PCgsOCwo5Qw4PCg8OCwoPDg8KCw4LCqC1Jw4PCg8OCwoLDg8KCw4LChcODw
+ oPDgsKDw4PCgsOCwoB1RMODwoPDgsKCw4PCgsOCwqFwek/Dg8KDw4LCgsODwoLDgsKLw4PCg8OCwo
+ PDg8KCw4LCj1DDg8KDw4LCg8ODwoLDgsKoScODwoPDgsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsK
+ AdTPDg8KDw4LCgsODwoLDgsKhbHpPw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo5Qw4PC
+ g8OCwoPDg8KCw4LCqEnDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCgHXDg8KDw4LCgsODw
+ oLDgsKhaHpPw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo9Qw4PCg8OCwoPDg8KCw4LCqM
+ ODwoPDgsKDw4PCgsOCwrpIw4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwoB1M8ODwoPDgsK
+ Dw4PCgsOCwoBfXsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLD
+ gsKCw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgjPDg8KDw4LCg8ODwoLDgsKAX17Dg
+ 8KDw4LCg8ODwoLDgsKCw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo7Dg8KDw4LCg8ODwo
+ LDgsKoJ8ODwoPDgsKDw4PCgsOCwq3Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODwoP
+ DgsKCw4PCgsOCwoPDg8KDw4LCg8ODwoLDgsK4aHU5w4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PC
+ gsOCwovDg8KDw4LCg8ODwoLDgsKOw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpDDg8KDw
+ 4LCg8ODwoLDgsKIw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgs
+ KIw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpLDg8KDw4LCg8ODwoLDgsKEw4PCg8OCwoL
+ Dg8KCw4LChcODwoPDgsKDw4PCgsOCwoB0IcODwoPDgsKCw4PCgsOCwovDg8KDw4LCgsODwoLDgsKA
+ w4PCg8OCwoPDg8KCw4LCtMODwoPDgsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsKAdGbDg8KDw4LCg
+ sODwoLDgsKLQGY9dGY9dTPDg8KDw4LCg8ODwoLDgsKAX17Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCwo
+ LDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwrhfXsODwoPDgsKDw4PCgsO
+ CwoIzw4PCg8OCwoPDg8KCw4LCgF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwovDg8KD
+ w4LCg8ODwoLDgsK/Ri9BUC9BRi9BWi9BZC9BWzBBZC9BZTBBZC9BZC9BbzBBZC9BeTBBw4PCg8OCw
+ oLDg8KCw4LCgzBBMUFhMUFrMUE=
+description:: UF7Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgsOC
+ wozDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgsOCwozDg8KDw4LCg
+ 8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCqFDDg8KDw4LCg8ODwoLDgsKpRsODwoPDgsKDw4PCgsOCwo
+ zDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgsOCwozDg8KDw4LCg8O
+ DwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKCw4PCgsOCwotEJCDDg8KDw4LCgsODwoLDgsKD
+ w4PCg8OCwoPDg8KCw4LCrMODwoPDgsKCw4PCgsOCwotUJCRTw4PCg8OCwoLDg8KCw4LCi1wkJFbDg
+ 8KDw4LCgsODwoLDgsKJTCRXVVBSU8ODwoPDgsKDw4PCgsOCwqjDg8KDw4LCg8ODwoLDgsKdT8ODwo
+ PDgsKCw4PCgsOCwoN8JDB1w4PCg8OCwoPDg8KCw4LCh8ODwoPDgsKDw4PCgsOCwoDDg8KDw4LCg8O
+ DwoLDgsKBTsODwoPDgsKDw4PCgsOCwqktw4PCg8OCwoLDg8KCw4LCg3wkMHTDg8KDw4LCgsODwoLD
+ gsKDfCQww4PCg8OCwoLDg8KCw4LChTPDg8KDw4LCg8ODwoLDgsK2OTXDg8KDw4LCg8ODwoLDgsKAw
+ 4PCg8OCwoPDg8KCw4LCgU7Dg8KDw4LCgsODwoLDgsKEIMODwoPDgsKCw4PCgsOCwqFIw4PCg8OCwo
+ PDg8KCw4LChU7Dg8KDw4LCgsODwoLDgsKJNcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCg8ODwoLDgsK
+ BTsODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKIw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKD
+ w4PCgsOCwr9TXMODwoPDgsKCw4PCgsOCwolEJDvDg8KDw4LCg8ODwoLDgsKGw4PCg8OCwoLDg8KCw
+ 4LChMODwoPDgsKCw4PCgsOCwpHDg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLIEjDg8
+ KDw4LCg8ODwoLDgsKFTlDDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCv1Ngw4PCg8OCwoL
+ Dg8KCw4LCi8ODwoPDgsKDw4PCgsOCwpjDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCm3Rx
+ w4PCg8OCwoLDg8KCw4LCizvDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCi8ODwoPDgsKDw
+ 4PCgsOCwr9XaMODwoPDgsKCw4PCgsOCwolEJDvDg8KDw4LCg8ODwoLDgsKGdGLDg8KDw4LCgsODwo
+ LDgsKLf2zDg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCi1D
+ Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCl8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8OD
+ woLDgsKow4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwq10SmgoT03Dg8KDw4LCgsODwoLDg
+ sKLw4PCg8OCwoPDg8KCw4LCjcODwoPDgsKDw4PCgsOCwqggTMODwoPDgsKCw4PCgsOCwoXDg8KDw4
+ LCg8ODwoLDgsKAdDrDg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLTSBQUcODwoPDgsK
+ Dw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoLDg8KCw4LCik/Dg8KDw4LCgsODwoLDgsKL
+ RCQoZitEJCDDg8KDw4LCgsODwoLDgsK3w4PCg8OCwoPDg8KCw4LCiMODwoPDgsKDw4PCgsOCwoHDg
+ 8KDw4LCg8ODwoLDgsKhw4PCg8OCwoLDg8KCw4LCi0QkJGYrRCTDg8KDw4LCgsODwoLDgsK3w4PCg8
+ OCwoPDg8KCw4LCkMODwoPDgsKDw4PCgsOCworDg8KDw4LCgsODwoLDgsKLRSBRVmpQw4PCg8OCwoP
+ Dg8KCw4LCv8ODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKKTzl0JHXDg8KDw4LCgsODwoLD
+ gsKhOXQkw4PCg8OCwoLDg8KCw4LChW/Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODw
+ oPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKhRMODwoPDgsKDw4PCgsOCwoVOw4PCg8OCwoLDg8
+ KCw4LCi8ODwoPDgsKDw4PCgsOCwojDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCv1Ncw4P
+ Cg8OCwoLDg8KCw4LCiUQkw4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsOD
+ woLDgsKEw4PCg8OCwoPDg8KCw4LCtjPDg8KDw4LCg8ODwoLDgsK2w4PCg8OCwoLDg8KCw4LCjUQkw
+ 4PCg8OCwoLDg8KCw4LCiyBEw4PCg8OCwoPDg8KCw4LChU5Qw4PCg8OCwoLDg8KCw4LCi8ODwoPDgs
+ KDw4PCgsOCwr9TYMODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsK4w4PCg8OCwoLDg8KCw4L
+ ChcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKEw4PCg8OCwoPDg8KCw4LCkMODwoPDgsKC
+ w4PCgsOCwovDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCj8ODwoPDgsKDw4PCgsOCwr9Ta
+ MODwoPDgsKCw4PCgsOCwolEJDvDg8KDw4LCg8ODwoLDgsKGw4PCg8OCwoLDg8KCw4LChMODwoPDgs
+ KCw4PCgsOCwr3Dg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4L
+ Cj1DDg8KDw4LCg8ODwoLDgsK/U2zDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCqMODwoPD
+ gsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsKtw4PCg8OCwoLDg8KCw4LChMODwoPDgsKCw4PCgsOCw
+ p9oMMODwoPDgsKDw4PCgsOCwolMw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo3Dg8KDw4
+ LCg8ODwoLDgsKow4PCg8OCwoPDg8KCw4LCq0vDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4L
+ CgMODwoPDgsKCw4PCgsOCwoTDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoLDg8KCw4LCi0QkOcODwoPD
+ gsKCw4PCgsOCwrDDg8KDw4LCg8ODwoLDgsKEdEU5w4PCg8OCwoLDg8KCw4LCtTR0PcODwoPDgsKCw
+ 4PCgsOCwovDg8KDw4LCg8ODwoLDgsKNw4PCg8OCwoPDg8KCw4LCqMODwoPDgsKDw4PCgsOCwo5Lw4
+ PCg8OCwoLDg8KCw4LCi0AgUMODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKsw4PCg8OCwoL
+ Dg8KCw4LCik/Dg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCgHUow4PCg8OCwoLDg8KCw4LC
+ i8ODwoPDgsKDw4PCgsOCwo3Dg8KDw4LCgsODwoLDgsKJw4PCg8OCwoLDg8KCw4LCtTTDg8KDw4LCg
+ 8ODwoLDgsKow4PCg8OCwoPDg8KCw4LCl8ODwoPDgsKDw4PCgsOCwrtWw4PCg8OCwoLDg8KCw4LCi8
+ ODwoPDgsKDw4PCgsOCwo3Dg8KDw4LCg8ODwoLDgsKow4PCg8OCwoLDg8KCw4LCnw==
+
+#LEAD COMMENT
+
+# another comment
+dn: CN=All Staff,ou=Groups,o=University of Michigan,c=TEST
+#EMBEDDED COMMENT
+member: cn=Manager,o=University of Michigan,c=TEST
+member: cn=Barbara Jensen,ou=Information Technology Division,ou=People,o=Unive
+ rsity of Michigan,c=TEST
+member: cn=Jane Doe,ou=Alumni Association,ou=People,o=University of Michigan,c
+ =US
+member: cn=John Doe,ou=Information Technology Division,ou=People,o=University
+ of Michigan,c=TEST
+member: cn=Mark Elliot,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+member: cn=James A Jones 1,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+member: cn=James A Jones 2,ou=Information Technology Division,ou=People,o=Univ
+ ersity of Michigan,c=TEST
+member: cn=Jennifer Smith,ou=Alumni Association,ou=People,o=University of Mich
+ igan,c=TEST
+member: cn=Dorothy Stevens,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+member: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+member: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,o=Univers
+ ity of Michigan,c=TEST
+owner: cn=Manager,o=University of Michigan,c=TEST
+cn: All Staff
+description: Everyone in the sample data
+objectclass: groupofnames
+
+dn: cn=Alumni Assoc Staff,ou=Groups,o=University of Michigan,c=TEST
+member: cn=Manager,o=University of Michigan,c=TEST
+member: cn=Dorothy Stevens,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+member: cn=James A Jones 1,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+member: cn=Jane Doe,ou=Alumni Association,ou=People,o=University of Michigan,c
+ =US
+member: cn=Jennifer Smith,ou=Alumni Association,ou=People,o=University of Mich
+ igan,c=TEST
+member: cn=Mark Elliot,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+member: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+owner: cn=Manager,o=University of Michigan,c=TEST
+description: All Alumni Assoc Staff
+cn: Alumni Assoc Staff
+objectclass: groupofnames
+
+dn: cn=Finance, cn=Alumni Assoc Staff,ou=Groups,o=University of Michigan,c=TEST
+member: cn=Manager,o=University of Michigan,c=TEST
+member: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Mic
+ higan,c=TEST
+owner: cn=Manager,o=University of Michigan,c=TEST
+description: Finance group
+cn: Finance
+objectclass: groupofnames
+
+dn: ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+objectclass: organizationalUnit
+ou: Alumni Association
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,o=Universit
+ y of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Barbara Jensen
+cn: Babs Jensen
+sn:: IEplbnNlbiA=
+uid:: YmplCW5zZW4
+title: Mythical Manager, Research Systems
+postaladdress: ITD Prod Dev & Deployment $ 535 W. William St. Room 4212 $ Ann
+ Arbor, MI 48103-4943
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+userpassword:: YmplbnNlbg==
+mail: bjensen@mailgw.example.com
+homepostaladdress: 123 Wesley $ Ann Arbor, MI 48103
+description: Mythical manager of the rsdd unix project
+drink: water
+homephone: +1 313 555 2333
+pager: +1 313 555 3233
+facsimiletelephonenumber: +1 313 555 2274
+telephonenumber: +1 313 555 9022
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,o=University
+ of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Bjorn Jensen
+cn: Biiff Jensen
+sn: Jensen
+uid: bjorn
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+userpassword:: Ympvcm4=
+homepostaladdress: 19923 Seven Mile Rd. $ South Lyon, MI 49999
+drink: Iced Tea
+description: Hiker, biker
+title: Director, Embedded Systems
+postaladdress: Info Tech Division $ 535 W. William St. $ Ann Arbor, MI 48103
+mail: bjorn@mailgw.example.com
+homephone: +1 313 555 5444
+pager: +1 313 555 4474
+facsimiletelephonenumber: +1 313 555 2177
+telephonenumber: +1 313 555 0355
+
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+objectclass: OpenLDAPperson
+cn: Dorothy Stevens
+cn: Dot Stevens
+sn: Stevens
+uid: dots
+title: Secretary, UM Alumni Association
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+drink: Lemonade
+homepostaladdress: 377 White St. Apt. 3 $ Ann Arbor, MI 48104
+description: Very tall
+facsimiletelephonenumber: +1 313 555 3223
+telephonenumber: +1 313 555 3664
+mail: dots@mail.alumni.example.com
+homephone: +1 313 555 0454
+
+dn: cn=ITD Staff,ou=Groups,o=University of Michigan,c=TEST
+owner: cn=Manager,o=University of Michigan,c=TEST
+description: All ITD Staff
+cn: ITD Staff
+objectclass: groupofuniquenames
+uniquemember: cn=Manager,o=University of Michigan,c=TEST
+uniquemember: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,o=U
+ niversity of Michigan,c=TEST
+uniquemember: cn=James A Jones 2,ou=Information Technology Division,ou=People,
+ o=University of Michigan,c=TEST
+uniquemember: cn=John Doe,ou=Information Technology Division,ou=People,o=Unive
+ rsity of Michigan,c=TEST
+
+dn: cn=James A Jones 1,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+objectclass: OpenLDAPperson
+cn: James A Jones 1
+cn: James Jones
+cn: Jim Jones
+sn: Jones
+uid: jaj
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+userpassword:: amFq
+homepostaladdress: 3882 Beverly Rd. $ Ann Arbor, MI 48105
+homephone: +1 313 555 4772
+description: Outstanding
+title: Mad Cow Researcher, UM Alumni Association
+pager: +1 313 555 3923
+mail: jaj@mail.alumni.example.com
+facsimiletelephonenumber: +1 313 555 4332
+telephonenumber: +1 313 555 0895
+
+dn: cn=James A Jones 2,ou=Information Technology Division,ou=People,o=Universi
+ ty of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: James A Jones 2
+cn: James Jones
+cn: Jim Jones
+sn: Doe
+uid: jjones
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+homepostaladdress: 933 Brooks $ Ann Arbor, MI 48104
+homephone: +1 313 555 8838
+title: Senior Manager, Information Technology Division
+description: Not around very much
+mail: jjones@mailgw.example.com
+postaladdress: Info Tech Division $ 535 W William $ Ann Arbor, MI 48103
+pager: +1 313 555 2833
+facsimiletelephonenumber: +1 313 555 8688
+telephonenumber: +1 313 555 7334
+
+dn: cn=Jane Doe,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Jane Doe
+cn: Jane Alverson
+sn: Doe
+uid: jdoe
+title: Programmer Analyst, UM Alumni Association
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+homepostaladdress: 123 Anystreet $ Ann Arbor, MI 48104
+drink: diet coke
+description: Enthusiastic
+mail: jdoe@woof.net
+homephone: +1 313 555 5445
+pager: +1 313 555 1220
+facsimiletelephonenumber: +1 313 555 2311
+telephonenumber: +1 313 555 4774
+
+dn: cn=Jennifer Smith,ou=Alumni Association,ou=People,o=University of Michigan
+ ,c=TEST
+objectclass: OpenLDAPperson
+cn: Jennifer Smith
+cn: Jen Smith
+sn: Smith
+uid: jen
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+drink: Sam Adams
+homepostaladdress: 1000 Maple #44 $ Ann Arbor, MI 48103
+title: Telemarketer, UM Alumni Association
+mail: jen@mail.alumni.example.com
+homephone: +1 313 555 2333
+pager: +1 313 555 6442
+facsimiletelephonenumber: +1 313 555 2756
+telephonenumber: +1 313 555 8232
+
+dn: cn=John Doe,ou=Information Technology Division,ou=People,o=University of M
+ ichigan,c=TEST
+objectclass: OpenLDAPperson
+cn: John Doe
+cn: Jonathon Doe
+sn: Doe
+uid: johnd
+postaladdress: ITD $ 535 W. William $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+homepostaladdress: 912 East Bllvd $ Ann Arbor, MI 48104
+title: System Administrator, Information Technology Division
+description: overworked!
+mail: johnd@mailgw.example.com
+homephone: +1 313 555 3774
+pager: +1 313 555 6573
+facsimiletelephonenumber: +1 313 555 4544
+telephonenumber: +1 313 555 9394
+
+dn: cn=Manager,o=University of Michigan,c=TEST
+objectclass: person
+cn: Manager
+cn: Directory Manager
+cn: Dir Man
+sn: Manager
+description: Manager of the directory
+userpassword:: c2VjcmV0
+
+dn: cn=Mark Elliot,ou=Alumni Association,ou=People,o=University of Michigan,c=
+ TEST
+objectclass: OpenLDAPperson
+cn: Mark Elliot
+cn: Mark A Elliot
+sn: Elliot
+uid: melliot
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+homepostaladdress: 199 Outer Drive $ Ypsilanti, MI 48198
+homephone: +1 313 555 0388
+drink: Gasoline
+title: Director, UM Alumni Association
+mail: melliot@mail.alumni.example.com
+pager: +1 313 555 7671
+facsimiletelephonenumber: +1 313 555 7762
+telephonenumber: +1 313 555 4177
+
+dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=University of Michiga
+ n,c=TEST
+objectclass: OpenLDAPperson
+cn: Ursula Hampster
+sn: Hampster
+uid: uham
+title: Secretary, UM Alumni Association
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff,ou=Groups,o=University of Michigan,c=TEST
+homepostaladdress: 123 Anystreet $ Ann Arbor, MI 48104
+mail: uham@mail.alumni.example.com
+description: a long attribute name, longer than 128 bytes so that we
+ trigger sign extension problems in tdb_pack, no that's not long enough
+ yet, maybe this is. I'll just keep going till it triggers the error
+homephone: +1 313 555 8421
+pager: +1 313 555 2844
+facsimiletelephonenumber: +1 313 555 9700
+telephonenumber: +1 313 555 5331
+
+dn: cn=Fred Bassett,ou=Alumni Association,ou=People,o=University of Michigan,c=TEST
+objectclass: OpenLDAPperson
+cn: Fred Bassett
+sn: Bassett
+uid: Bassett, Fred
+
+dn: o=ESCPI , o=CNAM,c=TEST
+objectclass: organizationalUnit
+o: ESCPI
+o: CNAM
+
+dn: o=ESCPI , uo=CNAM,c=TEST
+objectclass: organizationalUnit
+o: ESCPI
+uo: CNAM
+
+dn: cn=mat,o=ESCPI,o=CNAM,c=TEST
+objectclass: person
+cn: mat
diff --git a/lib/ldb/tests/test_ldb_dn.c b/lib/ldb/tests/test_ldb_dn.c
new file mode 100644
index 0000000..6bf621a
--- /dev/null
+++ b/lib/ldb/tests/test_ldb_dn.c
@@ -0,0 +1,232 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2018 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <ldb.h>
+#include "ldb_private.h"
+
+static void test_ldb_dn_add_child_fmt(void **state)
+{
+ struct ldb_context *ldb = ldb_init(NULL, NULL);
+
+ struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org");
+
+ assert_true(ldb_dn_add_child_fmt(dn,
+ "DC=X"));
+
+ assert_string_equal("DC=X,dc=samba,dc=org",
+ ldb_dn_get_linearized(dn));
+
+ assert_string_equal("DC=X,DC=SAMBA,DC=ORG",
+ ldb_dn_get_casefold(dn));
+
+}
+
+static void test_ldb_dn_add_child_fmt2(void **state)
+{
+ struct ldb_context *ldb = ldb_init(NULL, NULL);
+
+ struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org");
+
+ assert_true(ldb_dn_add_child_fmt(dn,
+ "DC=X,DC=Y"));
+
+ assert_string_equal("DC=X,DC=Y,dc=samba,dc=org",
+ ldb_dn_get_linearized(dn));
+
+ assert_string_equal("DC=X,DC=Y,DC=SAMBA,DC=ORG",
+ ldb_dn_get_casefold(dn));
+
+ assert_int_equal(4,
+ ldb_dn_get_comp_num(dn));
+
+}
+
+static void test_ldb_dn_add_child_val(void **state)
+{
+ struct ldb_context *ldb = ldb_init(NULL, NULL);
+
+ struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org");
+ struct ldb_val name = {.data = discard_const("X"),
+ .length = 1
+ };
+
+ assert_true(ldb_dn_add_child_val(dn,
+ "DC", name));
+
+ assert_string_equal("DC=X,dc=samba,dc=org",
+ ldb_dn_get_linearized(dn));
+
+ assert_string_equal("DC=X,DC=SAMBA,DC=ORG",
+ ldb_dn_get_casefold(dn));
+
+}
+
+static void test_ldb_dn_add_child_val2(void **state)
+{
+ struct ldb_context *ldb = ldb_init(NULL, NULL);
+
+ struct ldb_dn *dn = ldb_dn_new(ldb, ldb, "dc=samba,dc=org");
+
+ struct ldb_val name = {.data = discard_const("X,DC=Y"),
+ .length = 6
+ };
+
+ assert_true(ldb_dn_add_child_val(dn,
+ "DC", name));
+
+ assert_string_equal("DC=X\\,DC\\3DY,dc=samba,dc=org",
+ ldb_dn_get_linearized(dn));
+
+ assert_string_equal("DC=X\\,DC\\3DY,DC=SAMBA,DC=ORG",
+ ldb_dn_get_casefold(dn));
+
+ assert_int_equal(3,
+ ldb_dn_get_comp_num(dn));
+
+}
+
+struct explode_test {
+ const char *strdn;
+ int comp_num;
+ int ext_comp_num;
+ bool special;
+ bool invalid;
+ const char *linearized;
+ const char *ext_linearized_1;
+ bool explode_result;
+};
+
+static int extended_dn_read_ID(struct ldb_context *ldb, void *mem_ctx,
+ const struct ldb_val *in, struct ldb_val *out)
+{
+
+ /* Allow to check we can cope with validity checks */
+ if (in->length != 4) {
+ return -1;
+ }
+
+ *out = *in;
+ out->data = talloc_memdup(mem_ctx, in->data, in->length);
+ if (out->data == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* write out (reused for both HEX and clear for now) */
+static int extended_dn_write_ID(struct ldb_context *ldb, void *mem_ctx,
+ const struct ldb_val *in, struct ldb_val *out)
+{
+ *out = *in;
+
+ out->data = talloc_memdup(mem_ctx, in->data, in->length);
+ if (out->data == NULL) {
+ return -1;
+ }
+ return 0;
+}
+
+
+static void test_ldb_dn_explode(void **state)
+{
+ size_t i;
+ struct ldb_context *ldb = ldb_init(NULL, NULL);
+ struct explode_test tests[] = {
+ {"A=B", 1, 0, false, false, "A=B", "A=B", true},
+ {"", 0, 0, false, false, "", "", true},
+ {" ", -1, -1, false, false, " ", " ", false},
+ {"<>", 0, 0, false, false, "", NULL, true},
+ {"<", 0, 0, false, false, "", NULL, true},
+ {"<><", 0, 0, false, false, "", NULL, true},
+ {"<><>", 0, 0, false, false, "", NULL, true},
+ {"A=B,C=D", 2, 0, false, false, "A=B,C=D", "A=B,C=D", true},
+ {"<X=Y>A=B,C=D", -1, -1, false, false, "", NULL, false},
+ {"<X=Y>;A=B,C=D", -1, -1, false, false, "A=B,C=D", NULL, false},
+ {"<ID=ABC>;A=B,C=D", -1, -1, false, true, "A=B,C=D", NULL, false},
+ {"<ID=ABCD>;A=B,C=D", 2, 1, false, false, "A=B,C=D", "<ID=ABCD>;A=B,C=D", true},
+ {"x=🔥", 1, 0, false, false, "x=🔥", "x=🔥", true},
+ {"@FOO", 0, 0, true, false, "@FOO", "@FOO", true},
+ };
+
+ struct ldb_dn_extended_syntax syntax = {
+ .name = "ID",
+ .read_fn = extended_dn_read_ID,
+ .write_clear_fn = extended_dn_write_ID,
+ .write_hex_fn = extended_dn_write_ID
+ };
+
+ ldb_dn_extended_add_syntax(ldb, 0, &syntax);
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ bool result;
+ const char *linear;
+ const char *ext_linear;
+ struct ldb_dn *dn = ldb_dn_new(ldb, ldb, tests[i].strdn);
+
+ /*
+ * special, invalid, linear, and ext_linear are set before
+ * explode
+ */
+ linear = ldb_dn_get_linearized(dn);
+ assert_true((linear == NULL) == (tests[i].linearized == NULL));
+ assert_string_equal(linear,
+ tests[i].linearized);
+
+ ext_linear = ldb_dn_get_extended_linearized(ldb, dn, 1);
+ assert_true((ext_linear == NULL) ==
+ (tests[i].ext_linearized_1 == NULL));
+
+ if (tests[i].ext_linearized_1 != NULL) {
+ assert_string_equal(ext_linear,
+ tests[i].ext_linearized_1);
+ }
+ assert_true(ldb_dn_is_special(dn) == tests[i].special);
+ assert_true(ldb_dn_is_valid(dn) != tests[i].invalid);
+
+ /* comp nums are set by explode */
+ result = ldb_dn_validate(dn);
+ print_error("test %zu «%s»: res %i lin «%s» ext «%s»\n",
+ i, tests[i].strdn, result, linear, ext_linear);
+
+ assert_true(result == tests[i].explode_result);
+ assert_int_equal(ldb_dn_get_comp_num(dn),
+ tests[i].comp_num);
+ assert_int_equal(ldb_dn_get_extended_comp_num(dn),
+ tests[i].ext_comp_num);
+ }
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_ldb_dn_add_child_fmt),
+ cmocka_unit_test(test_ldb_dn_add_child_fmt2),
+ cmocka_unit_test(test_ldb_dn_add_child_val),
+ cmocka_unit_test(test_ldb_dn_add_child_val2),
+ cmocka_unit_test(test_ldb_dn_explode),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/test_ldb_qsort.c b/lib/ldb/tests/test_ldb_qsort.c
new file mode 100644
index 0000000..663cf0e
--- /dev/null
+++ b/lib/ldb/tests/test_ldb_qsort.c
@@ -0,0 +1,65 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2018 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <ldb.h>
+
+static int cmp_integer(int *a, int *b, void *opaque)
+{
+ if (a == NULL || b == NULL) {
+ return 0;
+ }
+
+ if (*a > *b) {
+ return 1;
+ }
+
+ if (*a < *b) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void test_ldb_qsort(void **state)
+{
+ int a[6] = { 6, 3, 2, 7, 9, 4 };
+
+ ldb_qsort(a, 6, sizeof(int), NULL, (ldb_qsort_cmp_fn_t)cmp_integer);
+
+ assert_int_equal(a[0], 2);
+ assert_int_equal(a[1], 3);
+ assert_int_equal(a[2], 4);
+ assert_int_equal(a[3], 6);
+ assert_int_equal(a[4], 7);
+ assert_int_equal(a[5], 9);
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_ldb_qsort),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/ldb/tests/testdata.txt b/lib/ldb/tests/testdata.txt
new file mode 100644
index 0000000..dadb9f0
--- /dev/null
+++ b/lib/ldb/tests/testdata.txt
@@ -0,0 +1,8 @@
+foo=bar5
+(&(|(a=b)(c=d))(e=f))
+(&(|(a=b)(c=d)(g=h))(e=f))
+name=firstname lastname
+(&(sid=S-1-2-3)(name = fred bloggs))
+(&(|(a=b)(c=d))(g=f))
+(&(sid=S-1-2-3)(!(name = fred bloggs)))
+(&(!(|(a=b)(c=d))(g=f)))
diff --git a/lib/ldb/tests/testsearch.txt b/lib/ldb/tests/testsearch.txt
new file mode 100644
index 0000000..c573863
--- /dev/null
+++ b/lib/ldb/tests/testsearch.txt
@@ -0,0 +1,5 @@
+(blah=foo)
+(objectclass=person)
+(dn=*)
+(&(objectclass=person)(objectclass=person))
+(&(objectclass=person)(objectclass=personx))