The Samba-Bugzilla – Attachment 14166 Details for
Bug 13335
after update to 4.8.0 DC failed with "Failed to find our own NTDS Settings objectGUID"
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
backported patch for 4.8
v4-8-guid-idx-fix.patch (text/plain), 98.61 KB, created by
Andrew Bartlett
on 2018-04-29 23:19:19 UTC
(
hide
)
Description:
backported patch for 4.8
Filename:
MIME Type:
Creator:
Andrew Bartlett
Created:
2018-04-29 23:19:19 UTC
Size:
98.61 KB
patch
obsolete
>From 07691960abe7063d45e9afee2a877c7ef395c07d Mon Sep 17 00:00:00 2001 >From: Gary Lockyer <gary@catalyst.net.nz> >Date: Wed, 28 Feb 2018 11:47:22 +1300 >Subject: [PATCH 1/6] ldb_tdb: Do not fail in GUID index mode if there is a > duplicate attribute > >It is not the job of the index code to enforce this, but do give a >a warning given it has been detected. > >However, now that we do allow it, we must never return the same >object twice to the caller, so filter for it in ltdb_index_filter(). > >The GUID list is sorted, which makes this cheap to handle, thankfully. > >Signed-off-by: Gary Lockyer <gary@catalyst.net.nz> >Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=13335 > >(cherry picked from commit 5c1504b94d1417894176811f18c5d450de22cfd2) >--- > lib/ldb/ldb_tdb/ldb_index.c | 64 ++++++++++++++++++++++++++++++++++++++++----- > 1 file changed, 57 insertions(+), 7 deletions(-) > >diff --git a/lib/ldb/ldb_tdb/ldb_index.c b/lib/ldb/ldb_tdb/ldb_index.c >index 99fef23662f..ee2027319e3 100644 >--- a/lib/ldb/ldb_tdb/ldb_index.c >+++ b/lib/ldb/ldb_tdb/ldb_index.c >@@ -1526,6 +1526,7 @@ static int ltdb_index_filter(struct ltdb_private *ltdb, > struct ldb_message *msg; > struct ldb_message *filtered_msg; > unsigned int i; >+ uint8_t previous_guid_key[LTDB_GUID_KEY_SIZE] = {}; > > ldb = ldb_module_get_ctx(ac->module); > >@@ -1538,11 +1539,6 @@ static int ltdb_index_filter(struct ltdb_private *ltdb, > int ret; > bool matched; > >- msg = ldb_msg_new(ac); >- if (!msg) { >- return LDB_ERR_OPERATIONS_ERROR; >- } >- > ret = ltdb_idx_to_key(ac->module, ltdb, > ac, &dn_list->dn[i], > &tdb_key); >@@ -1550,6 +1546,33 @@ static int ltdb_index_filter(struct ltdb_private *ltdb, > return ret; > } > >+ if (ltdb->cache->GUID_index_attribute != NULL) { >+ /* >+ * If we are in GUID index mode, then the dn_list is >+ * sorted. If we got a duplicate, forget about it, as >+ * otherwise we would send the same entry back more >+ * than once. >+ * >+ * This is needed in the truncated DN case, or if a >+ * duplicate was forced in via >+ * LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK >+ */ >+ >+ if (memcmp(previous_guid_key, tdb_key.dptr, >+ sizeof(previous_guid_key)) == 0) { >+ continue; >+ } >+ >+ memcpy(previous_guid_key, tdb_key.dptr, >+ sizeof(previous_guid_key)); >+ } >+ >+ msg = ldb_msg_new(ac); >+ if (!msg) { >+ return LDB_ERR_OPERATIONS_ERROR; >+ } >+ >+ > ret = ltdb_search_key(ac->module, ltdb, > tdb_key, msg, > LDB_UNPACK_DATA_FLAG_NO_DATA_ALLOC| >@@ -1923,9 +1946,36 @@ static int ltdb_index_add1(struct ldb_module *module, > BINARY_ARRAY_SEARCH_GTE(list->dn, list->count, > *key_val, ldb_val_equal_exact_ordered, > exact, next); >+ >+ /* >+ * Give a warning rather than fail, this could be a >+ * duplicate value in the record allowed by a caller >+ * forcing in the value with >+ * LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK >+ */ > if (exact != NULL) { >- talloc_free(list); >- return LDB_ERR_OPERATIONS_ERROR; >+ /* This can't fail, gives a default at worst */ >+ const struct ldb_schema_attribute *attr >+ = ldb_schema_attribute_by_name( >+ ldb, >+ ltdb->cache->GUID_index_attribute); >+ struct ldb_val v; >+ ret = attr->syntax->ldif_write_fn(ldb, list, >+ exact, &v); >+ if (ret == LDB_SUCCESS) { >+ ldb_debug(ldb, LDB_DEBUG_WARNING, >+ __location__ >+ ": duplicate attribute value in %s " >+ "for index on %s, " >+ "duplicate of %s %*.*s in %s", >+ ldb_dn_get_linearized(msg->dn), >+ el->name, >+ ltdb->cache->GUID_index_attribute, >+ (int)v.length, >+ (int)v.length, >+ v.data, >+ ldb_dn_get_linearized(dn_key)); >+ } > } > > if (next == NULL) { >-- >2.11.0 > > >From 38ba7cbcefc1819a0958af88b683b0a40e0f508d Mon Sep 17 00:00:00 2001 >From: Gary Lockyer <gary@catalyst.net.nz> >Date: Wed, 21 Feb 2018 15:12:40 +1300 >Subject: [PATCH 2/6] ldb_tdb: Add tests for truncated index keys > >Tests for the index truncation code as well as the GUID index >format in general. > >Covers truncation of both the DN and equality search keys. > >Signed-off-by: Gary Lockyer <gary@catalyst.net.nz> >Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> > >Autobuild-User(master): Andrew Bartlett <abartlet@samba.org> >Autobuild-Date(master): Sat Mar 3 09:58:40 CET 2018 on sn-devel-144 > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=13335 > >(cherry picked into 4.8 and cut down to operate without truncated >index values from master commit 4c0c888b571d4c21ab267024178353925a8c087c >by Andrew Bartlett) >--- > lib/ldb/tests/python/index.py | 1007 +++++++++++++++++++++++++++++++++++++++++ > lib/ldb/wscript | 2 +- > 2 files changed, 1008 insertions(+), 1 deletion(-) > create mode 100755 lib/ldb/tests/python/index.py > >diff --git a/lib/ldb/tests/python/index.py b/lib/ldb/tests/python/index.py >new file mode 100755 >index 00000000000..cd3735b5625 >--- /dev/null >+++ b/lib/ldb/tests/python/index.py >@@ -0,0 +1,1007 @@ >+#!/usr/bin/env python >+# >+# 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 index keys >+ >+This is a modified version of the test from master for databases such >+as lmdb have a maximum key length, instead just checking that the >+GUID index code still operates correctly. >+ >+Many of the test names are therefore incorrect, but are retained >+to keep the code easy to backport into if more tested are added in >+master. >+ >+""" >+ >+import os >+from unittest import TestCase >+import sys >+import ldb >+import shutil >+ >+PY3 = sys.version_info > (3, 0) >+ >+ >+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 MaxIndexKeyLengthTests(TestCase): >+ 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 50 >+ self.l = ldb.Ldb(self.filename, >+ options=[ >+ "modules:rdn_name", >+ "max_key_len_for_self_test:50"]) >+ self.l.add({"dn": "@ATTRIBUTES", >+ "uniqueThing": "UNIQUE_INDEX"}) >+ self.l.add({"dn": "@INDEXLIST", >+ "@IDXATTR": [b"uniqueThing", b"notUnique"], >+ "@IDXONE": [b"1"], >+ "@IDXGUID": [b"objectUUID"], >+ "@IDX_DN_GUID": [b"GUID"]}) >+ >+ # 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.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", >+ "objectUUID": b"0123456789abcde0"}) >+ >+ self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", >+ "objectUUID": b"0123456789abcde1"}) >+ >+ # 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.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM", >+ "objectUUID": b"0123456789abcde0"}) >+ >+ # Non conflicting rename, should succeed >+ self.l.rename("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG", >+ "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV") >+ >+ # 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.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", >+ "objectUUID": b"0123456789abcde1"}) >+ >+ 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"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"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", >+ 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.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", >+ "objectUUID": b"0123456789abcde1"}) >+ >+ 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.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", >+ "objectUUID": b"0123456789abcde1"}) >+ >+ 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.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.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.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"}) >+ >+ # 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.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.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.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.l.add({"dn": "OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4", >+ "objectUUID": 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.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV", >+ "objectUUID": b"0123456789abcde1"}) >+ >+ 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"}) >+ >+ # 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"}) >+ # >+ # 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"}) >+ >+ # 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 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"}) >+ >+ 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"}) >+ >+ 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.l.delete("OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG") >+ >+ # >+ # 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"}) >+ >+ res = self.l.search( >+ base="DC=SAMBA,DC=ORG", >+ expression="(notUnique=" + aa_gt_max.decode("ascii") + ")") >+ self.assertEquals(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. >+ # >+ >+ # >+ # 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) >+ >+ # >+ # 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) >+ >+ # >+ # 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) >+ >+ # >+ # 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) >+ >+ # >+ # 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) >+ >+ # >+ # 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.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.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"}) >+ >+ 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) >+ >+if __name__ == '__main__': >+ import unittest >+ unittest.TestProgram() >diff --git a/lib/ldb/wscript b/lib/ldb/wscript >index 6a204c0e42a..e14fa63ec2c 100644 >--- a/lib/ldb/wscript >+++ b/lib/ldb/wscript >@@ -374,7 +374,7 @@ def test(ctx): > if not os.path.exists(tmp_dir): > os.mkdir(tmp_dir) > pyret = samba_utils.RUN_PYTHON_TESTS( >- ['tests/python/api.py'], >+ ['tests/python/api.py', 'tests/python/index.py'], > extra_env={'SELFTEST_PREFIX': test_prefix}) > print("Python testsuite returned %d" % pyret) > >-- >2.11.0 > > >From 2815694bbad14d1d8c0c0c0f98d655414d2d5d35 Mon Sep 17 00:00:00 2001 >From: Andrew Bartlett <abartlet@samba.org> >Date: Mon, 26 Mar 2018 16:01:13 +1300 >Subject: [PATCH 3/6] ldb_tdb: Ensure we can not commit an index that is > corrupt due to partial re-index > >The re-index traverse can abort part-way though and we need to ensure >that the transaction is never committed as that will leave an un-useable db. > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=13335 > >Signed-off-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Gary Lockyer <gary@catalyst.net.nz> >(cherry picked from commit e481e4f30f4dc540f6f129b4f2faea48ee195673) >--- > lib/ldb/ldb_tdb/ldb_tdb.c | 30 ++++++++++++++++++++++++++++++ > lib/ldb/ldb_tdb/ldb_tdb.h | 2 ++ > 2 files changed, 32 insertions(+) > >diff --git a/lib/ldb/ldb_tdb/ldb_tdb.c b/lib/ldb/ldb_tdb/ldb_tdb.c >index 16e4b8ea26e..a530a454b29 100644 >--- a/lib/ldb/ldb_tdb/ldb_tdb.c >+++ b/lib/ldb/ldb_tdb/ldb_tdb.c >@@ -410,6 +410,10 @@ static int ltdb_modified(struct ldb_module *module, struct ldb_dn *dn) > ret = ltdb_cache_reload(module); > } > >+ if (ret != LDB_SUCCESS) { >+ ltdb->reindex_failed = true; >+ } >+ > return ret; > } > >@@ -1404,9 +1408,17 @@ static int ltdb_start_trans(struct ldb_module *module) > > ltdb_index_transaction_start(module); > >+ ltdb->reindex_failed = false; >+ > return LDB_SUCCESS; > } > >+/* >+ * Forward declaration to allow prepare_commit to in fact abort the >+ * transaction >+ */ >+static int ltdb_del_trans(struct ldb_module *module); >+ > static int ltdb_prepare_commit(struct ldb_module *module) > { > int ret; >@@ -1417,6 +1429,24 @@ static int ltdb_prepare_commit(struct ldb_module *module) > return LDB_SUCCESS; > } > >+ /* >+ * Check if the last re-index failed. >+ * >+ * This can happen if for example a duplicate value was marked >+ * unique. We must not write a partial re-index into the DB. >+ */ >+ if (ltdb->reindex_failed) { >+ /* >+ * We must instead abort the transaction so we get the >+ * old values and old index back >+ */ >+ ltdb_del_trans(module); >+ ldb_set_errstring(ldb_module_get_ctx(module), >+ "Failure during re-index, so " >+ "transaction must be aborted."); >+ return LDB_ERR_OPERATIONS_ERROR; >+ } >+ > ret = ltdb_index_transaction_commit(module); > if (ret != LDB_SUCCESS) { > tdb_transaction_cancel(ltdb->tdb); >diff --git a/lib/ldb/ldb_tdb/ldb_tdb.h b/lib/ldb/ldb_tdb/ldb_tdb.h >index 7e182495928..9591ee59bf1 100644 >--- a/lib/ldb/ldb_tdb/ldb_tdb.h >+++ b/lib/ldb/ldb_tdb/ldb_tdb.h >@@ -37,6 +37,8 @@ struct ltdb_private { > > bool read_only; > >+ bool reindex_failed; >+ > const struct ldb_schema_syntax *GUID_index_syntax; > }; > >-- >2.11.0 > > >From 95c3ec6a43efa65c37446500c1797d5f2de89cf4 Mon Sep 17 00:00:00 2001 >From: Gary Lockyer <gary@catalyst.net.nz> >Date: Tue, 6 Mar 2018 09:13:31 +1300 >Subject: [PATCH 4/6] lib ldb tests: Prepare to run api and index test on tdb > and lmdb > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=13335 > >Signed-off-by: Gary Lockyer <gary@catalyst.net.nz> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >(cherry picked from commit 06d9566ef7005588de18c5a1d07a5b9cd179d17b) >--- > lib/ldb/tests/python/api.py | 145 +++++++++++++++++++++++++----------------- > lib/ldb/tests/python/index.py | 29 ++++++++- > 2 files changed, 114 insertions(+), 60 deletions(-) > >diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py >index 409f446f1ea..eb6f4544ffb 100755 >--- a/lib/ldb/tests/python/api.py >+++ b/lib/ldb/tests/python/api.py >@@ -12,6 +12,9 @@ import shutil > > PY3 = sys.version_info > (3, 0) > >+TDB_PREFIX = "tdb://" >+MDB_PREFIX = "mdb://" >+ > > def tempdir(): > import tempfile >@@ -44,13 +47,36 @@ class NoContextTests(TestCase): > encoded2 = ldb.binary_encode('test\\x') > self.assertEqual(encoded2, encoded) > >-class SimpleLdb(TestCase): >+ >+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.filename) >+ self.ldb = ldb.Ldb(self.url(), flags=self.flags()) > > def tearDown(self): > shutil.rmtree(self.testdir) >@@ -58,16 +84,15 @@ class SimpleLdb(TestCase): > # Ensure the LDB is closed now, so we close the FD > del(self.ldb) > >- > def test_connect(self): >- ldb.Ldb(self.filename) >+ 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.filename) >+ x.connect(self.url(), flags=self.flags()) > > def test_repr(self): > x = ldb.Ldb() >@@ -82,7 +107,7 @@ class SimpleLdb(TestCase): > self.assertEqual([], x.modules()) > > def test_modules_tdb(self): >- x = ldb.Ldb(self.filename) >+ x = ldb.Ldb(self.url(), flags=self.flags()) > self.assertEqual("[<ldb module 'tdb'>]", repr(x.modules())) > > def test_firstmodule_none(self): >@@ -90,53 +115,53 @@ class SimpleLdb(TestCase): > self.assertEqual(x.firstmodule, None) > > def test_firstmodule_tdb(self): >- x = ldb.Ldb(self.filename) >+ 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.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > self.assertEqual(len(l.search()), 0) > > def test_search_controls(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > self.assertEqual(len(l.search(controls=["paged_results:0:5"])), 0) > > def test_search_attrs(self): >- l = ldb.Ldb(self.filename) >+ 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.filename) >+ 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.filename) >+ 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.filename) >+ 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.filename) >+ 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.filename) >+ 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.filename) >+ 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.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo1") > m["b"] = [b"a"] >@@ -145,10 +170,10 @@ class SimpleLdb(TestCase): > l.delete(m.dn) > > def test_contains(self): >- name = self.filename >- l = ldb.Ldb(name) >+ name = self.url() >+ l = ldb.Ldb(name, flags=self.flags()) > self.assertFalse(ldb.Dn(l, "dc=foo3") in l) >- l = ldb.Ldb(name) >+ l = ldb.Ldb(name, flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo3") > m["b"] = ["a"] >@@ -160,23 +185,23 @@ class SimpleLdb(TestCase): > l.delete(m.dn) > > def test_get_config_basedn(self): >- l = ldb.Ldb(self.filename) >+ 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.filename) >+ 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.filename) >+ 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.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > self.assertEqual(None, l.get_default_basedn()) > > def test_add(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo4") > m["bla"] = b"bla" >@@ -188,7 +213,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo4")) > > def test_search_iterator(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > s = l.search_iterator() > s.abandon() > try: >@@ -288,7 +313,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo5")) > > def test_add_text(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo4") > m["bla"] = "bla" >@@ -300,7 +325,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo4")) > > def test_add_w_unhandled_ctrl(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo4") > m["bla"] = b"bla" >@@ -308,7 +333,7 @@ class SimpleLdb(TestCase): > self.assertRaises(ldb.LdbError, lambda: l.add(m,["search_options:1:2"])) > > def test_add_dict(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = {"dn": ldb.Dn(l, "dc=foo5"), > "bla": b"bla"} > self.assertEqual(len(l.search()), 0) >@@ -319,7 +344,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo5")) > > def test_add_dict_text(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = {"dn": ldb.Dn(l, "dc=foo5"), > "bla": "bla"} > self.assertEqual(len(l.search()), 0) >@@ -330,7 +355,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo5")) > > def test_add_dict_string_dn(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = {"dn": "dc=foo6", "bla": b"bla"} > self.assertEqual(len(l.search()), 0) > l.add(m) >@@ -340,7 +365,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo6")) > > def test_add_dict_bytes_dn(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = {"dn": b"dc=foo6", "bla": b"bla"} > self.assertEqual(len(l.search()), 0) > l.add(m) >@@ -350,7 +375,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=foo6")) > > def test_rename(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo7") > m["bla"] = b"bla" >@@ -363,7 +388,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=bar")) > > def test_rename_string_dns(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=foo8") > m["bla"] = b"bla" >@@ -377,7 +402,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=bar")) > > def test_empty_dn(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > self.assertEqual(0, len(l.search())) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=empty") >@@ -394,7 +419,7 @@ class SimpleLdb(TestCase): > self.assertEqual(0, len(rm[0])) > > def test_modify_delete(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=modifydelete") > m["bla"] = [b"1234"] >@@ -417,7 +442,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=modifydelete")) > > def test_modify_delete_text(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=modifydelete") > m.text["bla"] = ["1234"] >@@ -440,7 +465,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=modifydelete")) > > def test_modify_add(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=add") > m["bla"] = [b"1234"] >@@ -458,7 +483,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=add")) > > def test_modify_add_text(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=add") > m.text["bla"] = ["1234"] >@@ -476,7 +501,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=add")) > > def test_modify_replace(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=modify2") > m["bla"] = [b"1234", b"456"] >@@ -496,7 +521,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=modify2")) > > def test_modify_replace_text(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=modify2") > m.text["bla"] = ["1234", "456"] >@@ -516,7 +541,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=modify2")) > > def test_modify_flags_change(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=add") > m["bla"] = [b"1234"] >@@ -542,7 +567,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=add")) > > def test_modify_flags_change_text(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > m = ldb.Message() > m.dn = ldb.Dn(l, "dc=add") > m.text["bla"] = ["1234"] >@@ -568,7 +593,7 @@ class SimpleLdb(TestCase): > l.delete(ldb.Dn(l, "dc=add")) > > def test_transaction_commit(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > l.transaction_start() > m = ldb.Message(ldb.Dn(l, "dc=foo9")) > m["foo"] = [b"bar"] >@@ -577,7 +602,7 @@ class SimpleLdb(TestCase): > l.delete(m.dn) > > def test_transaction_cancel(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > l.transaction_start() > m = ldb.Message(ldb.Dn(l, "dc=foo10")) > m["foo"] = [b"bar"] >@@ -588,12 +613,12 @@ class SimpleLdb(TestCase): > def test_set_debug(self): > def my_report_fn(level, text): > pass >- l = ldb.Ldb(self.filename) >+ 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.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > l.add({ > "dn" : b"dc=somedn", > "objectclass" : b"user", >@@ -605,10 +630,10 @@ class SimpleLdb(TestCase): > self.assertEqual(b"foo\0bar", res[0]["displayname"][0]) > > def test_no_crash_broken_expr(self): >- l = ldb.Ldb(self.filename) >+ l = ldb.Ldb(self.url(), flags=self.flags()) > self.assertRaises(ldb.LdbError,lambda: l.search("", ldb.SCOPE_SUBTREE, "&(dc=*)(dn=*)", ["dc"])) > >-class SearchTests(TestCase): >+class SearchTests(LdbBaseTest): > def tearDown(self): > shutil.rmtree(self.testdir) > super(SearchTests, self).tearDown() >@@ -621,7 +646,9 @@ class SearchTests(TestCase): > super(SearchTests, self).setUp() > self.testdir = tempdir() > self.filename = os.path.join(self.testdir, "search_test.ldb") >- self.l = ldb.Ldb(self.filename, options=["modules:rdn_name"]) >+ self.l = ldb.Ldb(self.url(), >+ flags=self.flags(), >+ options=["modules:rdn_name"]) > > self.l.add({"dn": "@ATTRIBUTES", > "DC": "CASE_INSENSITIVE"}) >@@ -1030,7 +1057,6 @@ class SearchTests(TestCase): > self.assertEqual(len(res11), 1) > > >- > class IndexedSearchTests(SearchTests): > """Test searches using the index, to ensure the index doesn't > break things""" >@@ -1091,6 +1117,7 @@ class GUIDIndexedSearchTests(SearchTests): > self.IDXGUID = True > self.IDXONE = True > >+ > class GUIDIndexedDNFilterSearchTests(SearchTests): > """Test searches using the index, to ensure the index doesn't > break things""" >@@ -1126,7 +1153,7 @@ class GUIDAndOneLevelIndexedSearchTests(SearchTests): > self.IDXONE = True > > >-class AddModifyTests(TestCase): >+class AddModifyTests(LdbBaseTest): > def tearDown(self): > shutil.rmtree(self.testdir) > super(AddModifyTests, self).tearDown() >@@ -1138,7 +1165,9 @@ class AddModifyTests(TestCase): > super(AddModifyTests, self).setUp() > self.testdir = tempdir() > self.filename = os.path.join(self.testdir, "add_test.ldb") >- self.l = ldb.Ldb(self.filename, options=["modules:rdn_name"]) >+ self.l = ldb.Ldb(self.url(), >+ flags=self.flags(), >+ options=["modules:rdn_name"]) > self.l.add({"dn": "DC=SAMBA,DC=ORG", > "name": b"samba.org", > "objectUUID": b"0123456789abcdef"}) >@@ -1266,6 +1295,7 @@ class AddModifyTests(TestCase): > "x": "z", "y": "a", > "objectUUID": b"0123456789abcde3"}) > >+ > class IndexedAddModifyTests(AddModifyTests): > """Test searches using the index, to ensure the index doesn't > break things""" >@@ -1378,7 +1408,6 @@ class TransIndexedAddModifyTests(IndexedAddModifyTests): > super(TransIndexedAddModifyTests, self).tearDown() > > >- > class DnTests(TestCase): > > def setUp(self): >@@ -1985,13 +2014,13 @@ class ModuleTests(TestCase): > l = ldb.Ldb(self.filename) > self.assertEqual(["init"], ops) > >-class LdbResultTests(TestCase): >+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.filename) >+ self.l = ldb.Ldb(self.url(), flags=self.flags()) > self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": b"samba.org"}) > self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": b"Admins"}) > self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": b"Users"}) >@@ -2099,7 +2128,7 @@ class LdbResultTests(TestCase): > del(self.l) > gc.collect() > >- child_ldb = ldb.Ldb(self.filename) >+ child_ldb = ldb.Ldb(self.url(), flags=self.flags()) > # start a transaction > child_ldb.transaction_start() > >@@ -2170,7 +2199,7 @@ class LdbResultTests(TestCase): > del(self.l) > gc.collect() > >- child_ldb = ldb.Ldb(self.filename) >+ child_ldb = ldb.Ldb(self.url(), flags=self.flags()) > # start a transaction > child_ldb.transaction_start() > >diff --git a/lib/ldb/tests/python/index.py b/lib/ldb/tests/python/index.py >index cd3735b5625..d8a84f26b4c 100755 >--- a/lib/ldb/tests/python/index.py >+++ b/lib/ldb/tests/python/index.py >@@ -37,6 +37,9 @@ import shutil > > PY3 = sys.version_info > (3, 0) > >+TDB_PREFIX = "tdb://" >+MDB_PREFIX = "mdb://" >+ > > def tempdir(): > import tempfile >@@ -57,7 +60,29 @@ def contains(result, dn): > return False > > >-class MaxIndexKeyLengthTests(TestCase): >+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 >@@ -94,7 +119,7 @@ class MaxIndexKeyLengthTests(TestCase): > self.testdir = tempdir() > self.filename = os.path.join(self.testdir, "key_len_test.ldb") > # Note that the maximum key length is set to 50 >- self.l = ldb.Ldb(self.filename, >+ self.l = ldb.Ldb(self.url(), > options=[ > "modules:rdn_name", > "max_key_len_for_self_test:50"]) >-- >2.11.0 > > >From 21babf74f89486a56a6a4153c91b727e8a061313 Mon Sep 17 00:00:00 2001 >From: Andrew Bartlett <abartlet@samba.org> >Date: Mon, 26 Mar 2018 16:07:45 +1300 >Subject: [PATCH 5/6] ldb: Add test to show a reindex failure must not leave > the DB corrupt > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=13335 > >Signed-off-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Gary Lockyer <gary@catalyst.net.nz> > >Autobuild-User(master): Andrew Bartlett <abartlet@samba.org> >Autobuild-Date(master): Thu Apr 5 07:53:10 CEST 2018 on sn-devel-144 > >(cherry picked from commit 653a0a1ba932fc0cc567253f3e153b2928505ba2) >--- > lib/ldb/tests/python/api.py | 160 ++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 160 insertions(+) > >diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py >index eb6f4544ffb..1167517fd5c 100755 >--- a/lib/ldb/tests/python/api.py >+++ b/lib/ldb/tests/python/api.py >@@ -1408,6 +1408,166 @@ class TransIndexedAddModifyTests(IndexedAddModifyTests): > super(TransIndexedAddModifyTests, 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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(len(res), 4) >+ >+ >+ 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 DnTests(TestCase): > > def setUp(self): >-- >2.11.0 > > >From 364a4d0fe5478ed020b04b428973b3aef30b958d Mon Sep 17 00:00:00 2001 >From: Andrew Bartlett <abartlet@samba.org> >Date: Mon, 30 Apr 2018 11:15:55 +1200 >Subject: [PATCH 6/6] ldb: Release ldb 1.3.3 > >* Fix failure to upgrade to the GUID index DB format >* Add tests for GUID index behaviour > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=13306 >Signed-off-by: Andrew Bartlett <abartlet@samba.org> >--- > lib/ldb/ABI/ldb-1.3.3.sigs | 279 ++++++++++++++++++++++++++++++++++++++ > lib/ldb/ABI/pyldb-util-1.3.3.sigs | 2 + > lib/ldb/wscript | 2 +- > 3 files changed, 282 insertions(+), 1 deletion(-) > create mode 100644 lib/ldb/ABI/ldb-1.3.3.sigs > create mode 100644 lib/ldb/ABI/pyldb-util-1.3.3.sigs > >diff --git a/lib/ldb/ABI/ldb-1.3.3.sigs b/lib/ldb/ABI/ldb-1.3.3.sigs >new file mode 100644 >index 00000000000..a31b84ef4b5 >--- /dev/null >+++ b/lib/ldb/ABI/ldb-1.3.3.sigs >@@ -0,0 +1,279 @@ >+ldb_add: int (struct ldb_context *, const struct ldb_message *) >+ldb_any_comparison: int (struct ldb_context *, void *, ldb_attr_handler_t, const struct ldb_val *, const struct ldb_val *) >+ldb_asprintf_errstring: void (struct ldb_context *, const char *, ...) >+ldb_attr_casefold: char *(TALLOC_CTX *, const char *) >+ldb_attr_dn: int (const char *) >+ldb_attr_in_list: int (const char * const *, const char *) >+ldb_attr_list_copy: const char **(TALLOC_CTX *, const char * const *) >+ldb_attr_list_copy_add: const char **(TALLOC_CTX *, const char * const *, const char *) >+ldb_base64_decode: int (char *) >+ldb_base64_encode: char *(TALLOC_CTX *, const char *, int) >+ldb_binary_decode: struct ldb_val (TALLOC_CTX *, const char *) >+ldb_binary_encode: char *(TALLOC_CTX *, struct ldb_val) >+ldb_binary_encode_string: char *(TALLOC_CTX *, const char *) >+ldb_build_add_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_build_del_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_build_extended_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const char *, void *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_build_mod_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_build_rename_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_build_search_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, const char *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_build_search_req_ex: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, struct ldb_parse_tree *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) >+ldb_casefold: char *(struct ldb_context *, TALLOC_CTX *, const char *, size_t) >+ldb_casefold_default: char *(void *, TALLOC_CTX *, const char *, size_t) >+ldb_check_critical_controls: int (struct ldb_control **) >+ldb_comparison_binary: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *) >+ldb_comparison_fold: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *) >+ldb_connect: int (struct ldb_context *, const char *, unsigned int, const char **) >+ldb_control_to_string: char *(TALLOC_CTX *, const struct ldb_control *) >+ldb_controls_except_specified: struct ldb_control **(struct ldb_control **, TALLOC_CTX *, struct ldb_control *) >+ldb_debug: void (struct ldb_context *, enum ldb_debug_level, const char *, ...) >+ldb_debug_add: void (struct ldb_context *, const char *, ...) >+ldb_debug_end: void (struct ldb_context *, enum ldb_debug_level) >+ldb_debug_set: void (struct ldb_context *, enum ldb_debug_level, const char *, ...) >+ldb_delete: int (struct ldb_context *, struct ldb_dn *) >+ldb_dn_add_base: bool (struct ldb_dn *, struct ldb_dn *) >+ldb_dn_add_base_fmt: bool (struct ldb_dn *, const char *, ...) >+ldb_dn_add_child: bool (struct ldb_dn *, struct ldb_dn *) >+ldb_dn_add_child_fmt: bool (struct ldb_dn *, const char *, ...) >+ldb_dn_alloc_casefold: char *(TALLOC_CTX *, struct ldb_dn *) >+ldb_dn_alloc_linearized: char *(TALLOC_CTX *, struct ldb_dn *) >+ldb_dn_canonical_ex_string: char *(TALLOC_CTX *, struct ldb_dn *) >+ldb_dn_canonical_string: char *(TALLOC_CTX *, struct ldb_dn *) >+ldb_dn_check_local: bool (struct ldb_module *, struct ldb_dn *) >+ldb_dn_check_special: bool (struct ldb_dn *, const char *) >+ldb_dn_compare: int (struct ldb_dn *, struct ldb_dn *) >+ldb_dn_compare_base: int (struct ldb_dn *, struct ldb_dn *) >+ldb_dn_copy: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *) >+ldb_dn_escape_value: char *(TALLOC_CTX *, struct ldb_val) >+ldb_dn_extended_add_syntax: int (struct ldb_context *, unsigned int, const struct ldb_dn_extended_syntax *) >+ldb_dn_extended_filter: void (struct ldb_dn *, const char * const *) >+ldb_dn_extended_syntax_by_name: const struct ldb_dn_extended_syntax *(struct ldb_context *, const char *) >+ldb_dn_from_ldb_val: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const struct ldb_val *) >+ldb_dn_get_casefold: const char *(struct ldb_dn *) >+ldb_dn_get_comp_num: int (struct ldb_dn *) >+ldb_dn_get_component_name: const char *(struct ldb_dn *, unsigned int) >+ldb_dn_get_component_val: const struct ldb_val *(struct ldb_dn *, unsigned int) >+ldb_dn_get_extended_comp_num: int (struct ldb_dn *) >+ldb_dn_get_extended_component: const struct ldb_val *(struct ldb_dn *, const char *) >+ldb_dn_get_extended_linearized: char *(TALLOC_CTX *, struct ldb_dn *, int) >+ldb_dn_get_ldb_context: struct ldb_context *(struct ldb_dn *) >+ldb_dn_get_linearized: const char *(struct ldb_dn *) >+ldb_dn_get_parent: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *) >+ldb_dn_get_rdn_name: const char *(struct ldb_dn *) >+ldb_dn_get_rdn_val: const struct ldb_val *(struct ldb_dn *) >+ldb_dn_has_extended: bool (struct ldb_dn *) >+ldb_dn_is_null: bool (struct ldb_dn *) >+ldb_dn_is_special: bool (struct ldb_dn *) >+ldb_dn_is_valid: bool (struct ldb_dn *) >+ldb_dn_map_local: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) >+ldb_dn_map_rebase_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) >+ldb_dn_map_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) >+ldb_dn_minimise: bool (struct ldb_dn *) >+ldb_dn_new: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *) >+ldb_dn_new_fmt: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *, ...) >+ldb_dn_remove_base_components: bool (struct ldb_dn *, unsigned int) >+ldb_dn_remove_child_components: bool (struct ldb_dn *, unsigned int) >+ldb_dn_remove_extended_components: void (struct ldb_dn *) >+ldb_dn_replace_components: bool (struct ldb_dn *, struct ldb_dn *) >+ldb_dn_set_component: int (struct ldb_dn *, int, const char *, const struct ldb_val) >+ldb_dn_set_extended_component: int (struct ldb_dn *, const char *, const struct ldb_val *) >+ldb_dn_update_components: int (struct ldb_dn *, const struct ldb_dn *) >+ldb_dn_validate: bool (struct ldb_dn *) >+ldb_dump_results: void (struct ldb_context *, struct ldb_result *, FILE *) >+ldb_error_at: int (struct ldb_context *, int, const char *, const char *, int) >+ldb_errstring: const char *(struct ldb_context *) >+ldb_extended: int (struct ldb_context *, const char *, void *, struct ldb_result **) >+ldb_extended_default_callback: int (struct ldb_request *, struct ldb_reply *) >+ldb_filter_from_tree: char *(TALLOC_CTX *, const struct ldb_parse_tree *) >+ldb_get_config_basedn: struct ldb_dn *(struct ldb_context *) >+ldb_get_create_perms: unsigned int (struct ldb_context *) >+ldb_get_default_basedn: struct ldb_dn *(struct ldb_context *) >+ldb_get_event_context: struct tevent_context *(struct ldb_context *) >+ldb_get_flags: unsigned int (struct ldb_context *) >+ldb_get_opaque: void *(struct ldb_context *, const char *) >+ldb_get_root_basedn: struct ldb_dn *(struct ldb_context *) >+ldb_get_schema_basedn: struct ldb_dn *(struct ldb_context *) >+ldb_global_init: int (void) >+ldb_handle_get_event_context: struct tevent_context *(struct ldb_handle *) >+ldb_handle_new: struct ldb_handle *(TALLOC_CTX *, struct ldb_context *) >+ldb_handle_use_global_event_context: void (struct ldb_handle *) >+ldb_handler_copy: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *) >+ldb_handler_fold: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *) >+ldb_init: struct ldb_context *(TALLOC_CTX *, struct tevent_context *) >+ldb_ldif_message_redacted_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *) >+ldb_ldif_message_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *) >+ldb_ldif_parse_modrdn: int (struct ldb_context *, const struct ldb_ldif *, TALLOC_CTX *, struct ldb_dn **, struct ldb_dn **, bool *, struct ldb_dn **, struct ldb_dn **) >+ldb_ldif_read: struct ldb_ldif *(struct ldb_context *, int (*)(void *), void *) >+ldb_ldif_read_file: struct ldb_ldif *(struct ldb_context *, FILE *) >+ldb_ldif_read_file_state: struct ldb_ldif *(struct ldb_context *, struct ldif_read_file_state *) >+ldb_ldif_read_free: void (struct ldb_context *, struct ldb_ldif *) >+ldb_ldif_read_string: struct ldb_ldif *(struct ldb_context *, const char **) >+ldb_ldif_write: int (struct ldb_context *, int (*)(void *, const char *, ...), void *, const struct ldb_ldif *) >+ldb_ldif_write_file: int (struct ldb_context *, FILE *, const struct ldb_ldif *) >+ldb_ldif_write_redacted_trace_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *) >+ldb_ldif_write_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *) >+ldb_load_modules: int (struct ldb_context *, const char **) >+ldb_map_add: int (struct ldb_module *, struct ldb_request *) >+ldb_map_delete: int (struct ldb_module *, struct ldb_request *) >+ldb_map_init: int (struct ldb_module *, const struct ldb_map_attribute *, const struct ldb_map_objectclass *, const char * const *, const char *, const char *) >+ldb_map_modify: int (struct ldb_module *, struct ldb_request *) >+ldb_map_rename: int (struct ldb_module *, struct ldb_request *) >+ldb_map_search: int (struct ldb_module *, struct ldb_request *) >+ldb_match_message: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, enum ldb_scope, bool *) >+ldb_match_msg: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope) >+ldb_match_msg_error: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope, bool *) >+ldb_match_msg_objectclass: int (const struct ldb_message *, const char *) >+ldb_mod_register_control: int (struct ldb_module *, const char *) >+ldb_modify: int (struct ldb_context *, const struct ldb_message *) >+ldb_modify_default_callback: int (struct ldb_request *, struct ldb_reply *) >+ldb_module_call_chain: char *(struct ldb_request *, TALLOC_CTX *) >+ldb_module_connect_backend: int (struct ldb_context *, const char *, const char **, struct ldb_module **) >+ldb_module_done: int (struct ldb_request *, struct ldb_control **, struct ldb_extended *, int) >+ldb_module_flags: uint32_t (struct ldb_context *) >+ldb_module_get_ctx: struct ldb_context *(struct ldb_module *) >+ldb_module_get_name: const char *(struct ldb_module *) >+ldb_module_get_ops: const struct ldb_module_ops *(struct ldb_module *) >+ldb_module_get_private: void *(struct ldb_module *) >+ldb_module_init_chain: int (struct ldb_context *, struct ldb_module *) >+ldb_module_load_list: int (struct ldb_context *, const char **, struct ldb_module *, struct ldb_module **) >+ldb_module_new: struct ldb_module *(TALLOC_CTX *, struct ldb_context *, const char *, const struct ldb_module_ops *) >+ldb_module_next: struct ldb_module *(struct ldb_module *) >+ldb_module_popt_options: struct poptOption **(struct ldb_context *) >+ldb_module_send_entry: int (struct ldb_request *, struct ldb_message *, struct ldb_control **) >+ldb_module_send_referral: int (struct ldb_request *, char *) >+ldb_module_set_next: void (struct ldb_module *, struct ldb_module *) >+ldb_module_set_private: void (struct ldb_module *, void *) >+ldb_modules_hook: int (struct ldb_context *, enum ldb_module_hook_type) >+ldb_modules_list_from_string: const char **(struct ldb_context *, TALLOC_CTX *, const char *) >+ldb_modules_load: int (const char *, const char *) >+ldb_msg_add: int (struct ldb_message *, const struct ldb_message_element *, int) >+ldb_msg_add_empty: int (struct ldb_message *, const char *, int, struct ldb_message_element **) >+ldb_msg_add_fmt: int (struct ldb_message *, const char *, const char *, ...) >+ldb_msg_add_linearized_dn: int (struct ldb_message *, const char *, struct ldb_dn *) >+ldb_msg_add_steal_string: int (struct ldb_message *, const char *, char *) >+ldb_msg_add_steal_value: int (struct ldb_message *, const char *, struct ldb_val *) >+ldb_msg_add_string: int (struct ldb_message *, const char *, const char *) >+ldb_msg_add_value: int (struct ldb_message *, const char *, const struct ldb_val *, struct ldb_message_element **) >+ldb_msg_canonicalize: struct ldb_message *(struct ldb_context *, const struct ldb_message *) >+ldb_msg_check_string_attribute: int (const struct ldb_message *, const char *, const char *) >+ldb_msg_copy: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *) >+ldb_msg_copy_attr: int (struct ldb_message *, const char *, const char *) >+ldb_msg_copy_shallow: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *) >+ldb_msg_diff: struct ldb_message *(struct ldb_context *, struct ldb_message *, struct ldb_message *) >+ldb_msg_difference: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message *, struct ldb_message *, struct ldb_message **) >+ldb_msg_element_compare: int (struct ldb_message_element *, struct ldb_message_element *) >+ldb_msg_element_compare_name: int (struct ldb_message_element *, struct ldb_message_element *) >+ldb_msg_element_equal_ordered: bool (const struct ldb_message_element *, const struct ldb_message_element *) >+ldb_msg_find_attr_as_bool: int (const struct ldb_message *, const char *, int) >+ldb_msg_find_attr_as_dn: struct ldb_dn *(struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, const char *) >+ldb_msg_find_attr_as_double: double (const struct ldb_message *, const char *, double) >+ldb_msg_find_attr_as_int: int (const struct ldb_message *, const char *, int) >+ldb_msg_find_attr_as_int64: int64_t (const struct ldb_message *, const char *, int64_t) >+ldb_msg_find_attr_as_string: const char *(const struct ldb_message *, const char *, const char *) >+ldb_msg_find_attr_as_uint: unsigned int (const struct ldb_message *, const char *, unsigned int) >+ldb_msg_find_attr_as_uint64: uint64_t (const struct ldb_message *, const char *, uint64_t) >+ldb_msg_find_common_values: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message_element *, struct ldb_message_element *, uint32_t) >+ldb_msg_find_duplicate_val: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message_element *, struct ldb_val **, uint32_t) >+ldb_msg_find_element: struct ldb_message_element *(const struct ldb_message *, const char *) >+ldb_msg_find_ldb_val: const struct ldb_val *(const struct ldb_message *, const char *) >+ldb_msg_find_val: struct ldb_val *(const struct ldb_message_element *, struct ldb_val *) >+ldb_msg_new: struct ldb_message *(TALLOC_CTX *) >+ldb_msg_normalize: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_message **) >+ldb_msg_remove_attr: void (struct ldb_message *, const char *) >+ldb_msg_remove_element: void (struct ldb_message *, struct ldb_message_element *) >+ldb_msg_rename_attr: int (struct ldb_message *, const char *, const char *) >+ldb_msg_sanity_check: int (struct ldb_context *, const struct ldb_message *) >+ldb_msg_sort_elements: void (struct ldb_message *) >+ldb_next_del_trans: int (struct ldb_module *) >+ldb_next_end_trans: int (struct ldb_module *) >+ldb_next_init: int (struct ldb_module *) >+ldb_next_prepare_commit: int (struct ldb_module *) >+ldb_next_read_lock: int (struct ldb_module *) >+ldb_next_read_unlock: int (struct ldb_module *) >+ldb_next_remote_request: int (struct ldb_module *, struct ldb_request *) >+ldb_next_request: int (struct ldb_module *, struct ldb_request *) >+ldb_next_start_trans: int (struct ldb_module *) >+ldb_op_default_callback: int (struct ldb_request *, struct ldb_reply *) >+ldb_options_find: const char *(struct ldb_context *, const char **, const char *) >+ldb_pack_data: int (struct ldb_context *, const struct ldb_message *, struct ldb_val *) >+ldb_parse_control_from_string: struct ldb_control *(struct ldb_context *, TALLOC_CTX *, const char *) >+ldb_parse_control_strings: struct ldb_control **(struct ldb_context *, TALLOC_CTX *, const char **) >+ldb_parse_tree: struct ldb_parse_tree *(TALLOC_CTX *, const char *) >+ldb_parse_tree_attr_replace: void (struct ldb_parse_tree *, const char *, const char *) >+ldb_parse_tree_copy_shallow: struct ldb_parse_tree *(TALLOC_CTX *, const struct ldb_parse_tree *) >+ldb_parse_tree_walk: int (struct ldb_parse_tree *, int (*)(struct ldb_parse_tree *, void *), void *) >+ldb_qsort: void (void * const, size_t, size_t, void *, ldb_qsort_cmp_fn_t) >+ldb_register_backend: int (const char *, ldb_connect_fn, bool) >+ldb_register_extended_match_rule: int (struct ldb_context *, const struct ldb_extended_match_rule *) >+ldb_register_hook: int (ldb_hook_fn) >+ldb_register_module: int (const struct ldb_module_ops *) >+ldb_rename: int (struct ldb_context *, struct ldb_dn *, struct ldb_dn *) >+ldb_reply_add_control: int (struct ldb_reply *, const char *, bool, void *) >+ldb_reply_get_control: struct ldb_control *(struct ldb_reply *, const char *) >+ldb_req_get_custom_flags: uint32_t (struct ldb_request *) >+ldb_req_is_untrusted: bool (struct ldb_request *) >+ldb_req_location: const char *(struct ldb_request *) >+ldb_req_mark_trusted: void (struct ldb_request *) >+ldb_req_mark_untrusted: void (struct ldb_request *) >+ldb_req_set_custom_flags: void (struct ldb_request *, uint32_t) >+ldb_req_set_location: void (struct ldb_request *, const char *) >+ldb_request: int (struct ldb_context *, struct ldb_request *) >+ldb_request_add_control: int (struct ldb_request *, const char *, bool, void *) >+ldb_request_done: int (struct ldb_request *, int) >+ldb_request_get_control: struct ldb_control *(struct ldb_request *, const char *) >+ldb_request_get_status: int (struct ldb_request *) >+ldb_request_replace_control: int (struct ldb_request *, const char *, bool, void *) >+ldb_request_set_state: void (struct ldb_request *, int) >+ldb_reset_err_string: void (struct ldb_context *) >+ldb_save_controls: int (struct ldb_control *, struct ldb_request *, struct ldb_control ***) >+ldb_schema_attribute_add: int (struct ldb_context *, const char *, unsigned int, const char *) >+ldb_schema_attribute_add_with_syntax: int (struct ldb_context *, const char *, unsigned int, const struct ldb_schema_syntax *) >+ldb_schema_attribute_by_name: const struct ldb_schema_attribute *(struct ldb_context *, const char *) >+ldb_schema_attribute_fill_with_syntax: int (struct ldb_context *, TALLOC_CTX *, const char *, unsigned int, const struct ldb_schema_syntax *, struct ldb_schema_attribute *) >+ldb_schema_attribute_remove: void (struct ldb_context *, const char *) >+ldb_schema_attribute_remove_flagged: void (struct ldb_context *, unsigned int) >+ldb_schema_attribute_set_override_handler: void (struct ldb_context *, ldb_attribute_handler_override_fn_t, void *) >+ldb_schema_set_override_GUID_index: void (struct ldb_context *, const char *, const char *) >+ldb_schema_set_override_indexlist: void (struct ldb_context *, bool) >+ldb_search: int (struct ldb_context *, TALLOC_CTX *, struct ldb_result **, struct ldb_dn *, enum ldb_scope, const char * const *, const char *, ...) >+ldb_search_default_callback: int (struct ldb_request *, struct ldb_reply *) >+ldb_sequence_number: int (struct ldb_context *, enum ldb_sequence_type, uint64_t *) >+ldb_set_create_perms: void (struct ldb_context *, unsigned int) >+ldb_set_debug: int (struct ldb_context *, void (*)(void *, enum ldb_debug_level, const char *, va_list), void *) >+ldb_set_debug_stderr: int (struct ldb_context *) >+ldb_set_default_dns: void (struct ldb_context *) >+ldb_set_errstring: void (struct ldb_context *, const char *) >+ldb_set_event_context: void (struct ldb_context *, struct tevent_context *) >+ldb_set_flags: void (struct ldb_context *, unsigned int) >+ldb_set_modules_dir: void (struct ldb_context *, const char *) >+ldb_set_opaque: int (struct ldb_context *, const char *, void *) >+ldb_set_require_private_event_context: void (struct ldb_context *) >+ldb_set_timeout: int (struct ldb_context *, struct ldb_request *, int) >+ldb_set_timeout_from_prev_req: int (struct ldb_context *, struct ldb_request *, struct ldb_request *) >+ldb_set_utf8_default: void (struct ldb_context *) >+ldb_set_utf8_fns: void (struct ldb_context *, void *, char *(*)(void *, void *, const char *, size_t)) >+ldb_setup_wellknown_attributes: int (struct ldb_context *) >+ldb_should_b64_encode: int (struct ldb_context *, const struct ldb_val *) >+ldb_standard_syntax_by_name: const struct ldb_schema_syntax *(struct ldb_context *, const char *) >+ldb_strerror: const char *(int) >+ldb_string_to_time: time_t (const char *) >+ldb_string_utc_to_time: time_t (const char *) >+ldb_timestring: char *(TALLOC_CTX *, time_t) >+ldb_timestring_utc: char *(TALLOC_CTX *, time_t) >+ldb_transaction_cancel: int (struct ldb_context *) >+ldb_transaction_cancel_noerr: int (struct ldb_context *) >+ldb_transaction_commit: int (struct ldb_context *) >+ldb_transaction_prepare_commit: int (struct ldb_context *) >+ldb_transaction_start: int (struct ldb_context *) >+ldb_unpack_data: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *) >+ldb_unpack_data_only_attr_list: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *, const char * const *, unsigned int, unsigned int *) >+ldb_unpack_data_only_attr_list_flags: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *, const char * const *, unsigned int, unsigned int, unsigned int *) >+ldb_val_dup: struct ldb_val (TALLOC_CTX *, const struct ldb_val *) >+ldb_val_equal_exact: int (const struct ldb_val *, const struct ldb_val *) >+ldb_val_map_local: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *) >+ldb_val_map_remote: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *) >+ldb_val_string_cmp: int (const struct ldb_val *, const char *) >+ldb_val_to_time: int (const struct ldb_val *, time_t *) >+ldb_valid_attr_name: int (const char *) >+ldb_vdebug: void (struct ldb_context *, enum ldb_debug_level, const char *, va_list) >+ldb_wait: int (struct ldb_handle *, enum ldb_wait_type) >diff --git a/lib/ldb/ABI/pyldb-util-1.3.3.sigs b/lib/ldb/ABI/pyldb-util-1.3.3.sigs >new file mode 100644 >index 00000000000..74d6719d2bc >--- /dev/null >+++ b/lib/ldb/ABI/pyldb-util-1.3.3.sigs >@@ -0,0 +1,2 @@ >+pyldb_Dn_FromDn: PyObject *(struct ldb_dn *) >+pyldb_Object_AsDn: bool (TALLOC_CTX *, PyObject *, struct ldb_context *, struct ldb_dn **) >diff --git a/lib/ldb/wscript b/lib/ldb/wscript >index e14fa63ec2c..75ccff5a1cf 100644 >--- a/lib/ldb/wscript >+++ b/lib/ldb/wscript >@@ -1,7 +1,7 @@ > #!/usr/bin/env python > > APPNAME = 'ldb' >-VERSION = '1.3.2' >+VERSION = '1.3.3' > > blddir = 'bin' > >-- >2.11.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Flags:
abartlet
:
review?
(
gary
)
metze
:
review+
Actions:
View
Attachments on
bug 13335
:
14114
| 14166