Bump version to 2.9.0

- Resolves: RHEL-165978 - Replication halt caused by an incorrect setting of "nsslapd-changelogmaxage"
- Resolves: RHEL-166003 - Crash in replica_config_add when manually configuring a replica with an incorrect nsds5ReplicaRoot.
- Resolves: RHEL-166004 - Possible memory leak when using the Retro Changelog plugin.
- Resolves: RHEL-168908 - ns-slapd fails to shutdown when deferred memberof update is in progress
- Resolves: RHEL-168964 - Web console doesn't show the sub suffix of ou=foo,ou=people,dc=example,dc=com. [rhel-9]
- Resolves: RHEL-168972 - Replica installation is failing with message MDB_BAD_VALSIZE: Unsupported size of key/DB name/data, or wrong DUPFIXED [rhel-9]
- Resolves: RHEL-169530 - Rebase 389-ds-base to 2.9.x
- Resolves: RHEL-170270 - DS 12 does not handle escape char in bind user [rhel-9]
- Resolves: RHEL-170275 - dnaSharedConfig: "dnaPortNum: 0" [rhel-9]
- Resolves: RHEL-170280 - Memory leaks in syncrepl plugin during persistent search operations [rhel-9]
- Resolves: RHEL-170287 - access log - suspicious wtime  optime negative and large values in internal op [rhel-9]
- Resolves: RHEL-170477 - An online reinitialization with LMDB is terminating the receiving server [rhel-9]
- Resolves: RHEL-170480 - dsctl healthcheck DSMOLE0001 inaccurate recommendations when there is more than 1 LDAP backend [rhel-9]
- Resolves: RHEL-170651 - passwordbadwords attribute of the local password policy is not functioning [rhel-9]
- Resolves: RHEL-170732 - Memory leaks in IPA context on server restart [rhel-9]
- Resolves: RHEL-174525 - [RFE] Add OS-level thread names to all server threads [rhel-9]
- Resolves: RHEL-180717 - Online export is failing when using the option "-s" [rhel-9]
This commit is contained in:
Viktor Ashirov 2026-06-05 11:24:42 +02:00
parent 4c921f32ff
commit c824da947d
49 changed files with 1965 additions and 13016 deletions

View File

@ -1,80 +0,0 @@
From 94a52cd03a6fcccf78a4d7e5c4adc65fb777fcfb Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 10 Nov 2025 13:20:28 +0100
Subject: [PATCH] Issue 7049 - RetroCL plugin generates invalid LDIF
Bug Description:
When a replicated modification marked with LDAP_MOD_IGNORE is logged,
`changes` attribute contains invalid LDIF:
```
replace: modifiersName
modifiersName: cn=MemberOf Plugin,cn=plugins,cn=config
-
modifyTimestamp: 20250903092211Z
-
```
Line `replace: modifyTimestamp` is missing.
A similar issue is present in audit log:
```
time: 20251031064114
dn: ou=tuser,dc=example,dc=com
result: 0
changetype: modify
add: objectClass
objectClass: nsMemberOf
-
replace: modifiersName
modifiersName: cn=MemberOf Plugin,cn=plugins,cn=config
-
-
```
Dash separator is logged, while the operation is not.
This issue is not present wheh JSON format is used.
Fix Description:
* retrocl_po.c: add a default case to skip the entire modification if it
has LDAP_MOD_IGNORE flag.
* auditlog.c: write the dash separator only if operation type is not
LDAP_MOD_IGNORE
Fixes: https://github.com/389ds/389-ds-base/issues/7049
Reviewed by: @progier389 (Thanks!)
---
ldap/servers/plugins/retrocl/retrocl_po.c | 3 +++
ldap/servers/slapd/auditlog.c | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/plugins/retrocl/retrocl_po.c b/ldap/servers/plugins/retrocl/retrocl_po.c
index e447ccdfb..277711dd3 100644
--- a/ldap/servers/plugins/retrocl/retrocl_po.c
+++ b/ldap/servers/plugins/retrocl/retrocl_po.c
@@ -99,6 +99,9 @@ make_changes_string(LDAPMod **ldm, const char **includeattrs)
addlenstr(l, ldm[i]->mod_type);
addlenstr(l, "\n");
break;
+ default:
+ /* LDAP_MOD_IGNORE or unknown - skip this mod entirely */
+ continue;
}
for (j = 0; ldm[i]->mod_bvalues != NULL &&
ldm[i]->mod_bvalues[j] != NULL;
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
index 32fcea38a..e2db40722 100644
--- a/ldap/servers/slapd/auditlog.c
+++ b/ldap/servers/slapd/auditlog.c
@@ -853,8 +853,8 @@ write_audit_file(
slapi_ch_free((void **)&buf);
}
}
+ addlenstr(l, "-\n");
}
- addlenstr(l, "-\n");
}
break;
--
2.52.0

View File

@ -0,0 +1,45 @@
From 71cda087462c7cef744555676603c7c5fde3ca55 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 3 Jun 2026 17:52:09 -0400
Subject: [PATCH 1/8] Issue 7554 - deref plugin null pointer dereference if
ber_init fails
Description:
**CWE**: CWE-476 (NULL Pointer Dereference)
A flaw in the 389 Directory Server's dereference control plugin allows an
unauthenticated attacker to crash the LDAP server when the system is under
memory pressure(OOM). The deref plugin, enabled by default, fails to check for
a memory allocation failure before using the result, causing the server
process to terminate.
CI test 'test_deref_and_access_control' already covers this fix.
relates: https://github.com/389ds/389-ds-base/issues/7554
Reviewed by: tbordaz & progier(Thanks!!)
---
ldap/servers/plugins/deref/deref.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ldap/servers/plugins/deref/deref.c b/ldap/servers/plugins/deref/deref.c
index fc1c10f71..fc157f6d6 100644
--- a/ldap/servers/plugins/deref/deref.c
+++ b/ldap/servers/plugins/deref/deref.c
@@ -357,6 +357,12 @@ deref_parse_ctrl_value(DerefSpecList *speclist, const struct berval *ctrlbv, int
}
ber = ber_init((struct berval *)ctrlbv);
+ if (!ber) {
+ *ldapcode = LDAP_UNWILLING_TO_PERFORM;
+ *ldaperrtext = "Deref control parsing failed to initialize BER element";
+ return;
+ }
+
for (tag = ber_first_element(ber, &len, &last);
(tag != LBER_ERROR) && (tag != LBER_END_OF_SEQORSET);
tag = ber_next_element(ber, &len, last)) {
--
2.54.0

View File

@ -0,0 +1,37 @@
From d42d7eefa937d4f12e21929210786274f9c8e849 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Thu, 4 Jun 2026 19:21:30 -0700
Subject: [PATCH 2/8] Issue 3555 - UI - Fix audit issue with npm -
brace-expansion (#7556)
Description: Run npm audit fix to address the vulnerability
in brace-expansion.
Relates: https://github.com/389ds/389-ds-base/issues/3555
Relates: https://github.com/389ds/389-ds-base/issues/7527
Reviewed by: jchapma (Thanks!)
---
src/cockpit/389-console/package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/cockpit/389-console/package-lock.json b/src/cockpit/389-console/package-lock.json
index 6c8ccdbd8..1efed1c1d 100644
--- a/src/cockpit/389-console/package-lock.json
+++ b/src/cockpit/389-console/package-lock.json
@@ -2377,9 +2377,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
--
2.54.0

View File

@ -1,318 +0,0 @@
From 67118939bd2b51e53f7284c2058a43744d1dba76 Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Wed, 7 Jan 2026 11:21:12 +0100
Subject: [PATCH] Issue 7096 - During replication online total init the
function idl_id_is_in_idlist is not scaling with large database (#7145)
Bug description:
During a online total initialization, the supplier sorts
the candidate list of entries so that the parents are sent before
children entries.
With large DB the ID array used for the sorting is not
scaling. It takes so long to build the candidate list that
the connection gets closed
Fix description:
Instead of using an ID array, uses a list of ID ranges
fixes: #7096
Reviewed by: Mark Reynolds, Pierre Rogier (Thanks !!)
---
ldap/servers/slapd/back-ldbm/back-ldbm.h | 12 ++
ldap/servers/slapd/back-ldbm/idl_common.c | 163 ++++++++++++++++++
ldap/servers/slapd/back-ldbm/idl_new.c | 30 ++--
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 3 +
4 files changed, 189 insertions(+), 19 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
index 36aa432e0..5e4988782 100644
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
@@ -281,6 +281,18 @@ typedef struct _idlist_set
#define INDIRECT_BLOCK(idl) ((idl)->b_nids == INDBLOCK)
#define IDL_NIDS(idl) (idl ? (idl)->b_nids : (NIDS)0)
+/*
+ * used by the supplier during online total init
+ * it stores the ranges of ID that are already present
+ * in the candidate list ('parentid>=1')
+ */
+typedef struct IdRange {
+ ID first;
+ ID last;
+ struct IdRange *next;
+} IdRange_t;
+
+
typedef size_t idl_iterator;
/* small hashtable implementation used in the entry cache -- the table
diff --git a/ldap/servers/slapd/back-ldbm/idl_common.c b/ldap/servers/slapd/back-ldbm/idl_common.c
index fcb0ece4b..fdc9b4e67 100644
--- a/ldap/servers/slapd/back-ldbm/idl_common.c
+++ b/ldap/servers/slapd/back-ldbm/idl_common.c
@@ -172,6 +172,169 @@ idl_min(IDList *a, IDList *b)
return (a->b_nids > b->b_nids ? b : a);
}
+/*
+ * This is a faster version of idl_id_is_in_idlist.
+ * idl_id_is_in_idlist uses an array of ID so lookup is expensive
+ * idl_id_is_in_idlist_ranges uses a list of ranges of ID lookup is faster
+ * returns
+ * 1: 'id' is present in idrange_list
+ * 0: 'id' is not present in idrange_list
+ */
+int
+idl_id_is_in_idlist_ranges(IDList *idl, IdRange_t *idrange_list, ID id)
+{
+ IdRange_t *range = idrange_list;
+ int found = 0;
+
+ if (NULL == idl || NOID == id) {
+ return 0; /* not in the list */
+ }
+ if (ALLIDS(idl)) {
+ return 1; /* in the list */
+ }
+
+ for(;range; range = range->next) {
+ if (id > range->last) {
+ /* check if it belongs to the next range */
+ continue;
+ }
+ if (id >= range->first) {
+ /* It belongs to that range [first..last ] */
+ found = 1;
+ break;
+ } else {
+ /* this range is after id */
+ break;
+ }
+ }
+ return found;
+}
+
+/* This function is used during the online total initialisation
+ * (see next function)
+ * It frees all ranges of ID in the list
+ */
+void idrange_free(IdRange_t **head)
+{
+ IdRange_t *curr, *sav;
+
+ if ((head == NULL) || (*head == NULL)) {
+ return;
+ }
+ curr = *head;
+ sav = NULL;
+ for (; curr;) {
+ sav = curr;
+ curr = curr->next;
+ slapi_ch_free((void *) &sav);
+ }
+ if (sav) {
+ slapi_ch_free((void *) &sav);
+ }
+ *head = NULL;
+}
+
+/* This function is used during the online total initialisation
+ * Because a MODRDN can move entries under a parent that
+ * has a higher ID we need to sort the IDList so that parents
+ * are sent, to the consumer, before the children are sent.
+ * The sorting with a simple IDlist does not scale instead
+ * a list of IDs ranges is much faster.
+ * In that list we only ADD/lookup ID.
+ */
+IdRange_t *idrange_add_id(IdRange_t **head, ID id)
+{
+ if (head == NULL) {
+ slapi_log_err(SLAPI_LOG_ERR, "idrange_add_id",
+ "Can not add ID %d in non defined list\n", id);
+ return NULL;
+ }
+
+ if (*head == NULL) {
+ /* This is the first range */
+ IdRange_t *new_range = (IdRange_t *)slapi_ch_malloc(sizeof(IdRange_t));
+ new_range->first = id;
+ new_range->last = id;
+ new_range->next = NULL;
+ *head = new_range;
+ return *head;
+ }
+
+ IdRange_t *curr = *head, *prev = NULL;
+
+ /* First, find if id already falls within any existing range, or it is adjacent to any */
+ while (curr) {
+ if (id >= curr->first && id <= curr->last) {
+ /* inside a range, nothing to do */
+ return curr;
+ }
+
+ if (id == curr->last + 1) {
+ /* Extend this range upwards */
+ curr->last = id;
+
+ /* Check for possible merge with next range */
+ IdRange_t *next = curr->next;
+ if (next && curr->last + 1 >= next->first) {
+ slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
+ "(id=%d) merge current with next range [%d..%d]\n", id, curr->first, curr->last);
+ curr->last = (next->last > curr->last) ? next->last : curr->last;
+ curr->next = next->next;
+ slapi_ch_free((void*) &next);
+ } else {
+ slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
+ "(id=%d) extend forward current range [%d..%d]\n", id, curr->first, curr->last);
+ }
+ return curr;
+ }
+
+ if (id + 1 == curr->first) {
+ /* Extend this range downwards */
+ curr->first = id;
+
+ /* Check for possible merge with previous range */
+ if (prev && prev->last + 1 >= curr->first) {
+ prev->last = curr->last;
+ prev->next = curr->next;
+ slapi_ch_free((void *) &curr);
+ slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
+ "(id=%d) merge current with previous range [%d..%d]\n", id, prev->first, prev->last);
+ return prev;
+ } else {
+ slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
+ "(id=%d) extend backward current range [%d..%d]\n", id, curr->first, curr->last);
+ return curr;
+ }
+ }
+
+ /* If id is before the current range, break so we can insert before */
+ if (id < curr->first) {
+ break;
+ }
+
+ prev = curr;
+ curr = curr->next;
+ }
+ /* Need to insert a new standalone IdRange */
+ IdRange_t *new_range = (IdRange_t *)slapi_ch_malloc(sizeof(IdRange_t));
+ new_range->first = id;
+ new_range->last = id;
+ new_range->next = curr;
+
+ if (prev) {
+ slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
+ "(id=%d) add new range [%d..%d]\n", id, new_range->first, new_range->last);
+ prev->next = new_range;
+ } else {
+ /* Insert at head */
+ slapi_log_err(SLAPI_LOG_REPL, "idrange_add_id",
+ "(id=%d) head range [%d..%d]\n", id, new_range->first, new_range->last);
+ *head = new_range;
+ }
+ return *head;
+}
+
+
int
idl_id_is_in_idlist(IDList *idl, ID id)
{
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
index 5fbcaff2e..2d978353f 100644
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
@@ -417,7 +417,6 @@ idl_new_range_fetch(
{
int ret = 0;
int ret2 = 0;
- int idl_rc = 0;
dbi_cursor_t cursor = {0};
IDList *idl = NULL;
dbi_val_t cur_key = {0};
@@ -436,6 +435,7 @@ idl_new_range_fetch(
size_t leftoverlen = 32;
size_t leftovercnt = 0;
char *index_id = get_index_name(be, db, ai);
+ IdRange_t *idrange_list = NULL;
if (NULL == flag_err) {
@@ -578,10 +578,12 @@ idl_new_range_fetch(
* found entry is the one from the suffix
*/
suffix = key;
- idl_rc = idl_append_extend(&idl, id);
- } else if ((key == suffix) || idl_id_is_in_idlist(idl, key)) {
+ idl_append_extend(&idl, id);
+ idrange_add_id(&idrange_list, id);
+ } else if ((key == suffix) || idl_id_is_in_idlist_ranges(idl, idrange_list, key)) {
/* the parent is the suffix or already in idl. */
- idl_rc = idl_append_extend(&idl, id);
+ idl_append_extend(&idl, id);
+ idrange_add_id(&idrange_list, id);
} else {
/* Otherwise, keep the {key,id} in leftover array */
if (!leftover) {
@@ -596,13 +598,7 @@ idl_new_range_fetch(
leftovercnt++;
}
} else {
- idl_rc = idl_append_extend(&idl, id);
- }
- if (idl_rc) {
- slapi_log_err(SLAPI_LOG_ERR, "idl_new_range_fetch",
- "Unable to extend id list (err=%d)\n", idl_rc);
- idl_free(&idl);
- goto error;
+ idl_append_extend(&idl, id);
}
count++;
@@ -695,21 +691,17 @@ error:
while(remaining > 0) {
for (size_t i = 0; i < leftovercnt; i++) {
- if (leftover[i].key > 0 && idl_id_is_in_idlist(idl, leftover[i].key) != 0) {
+ if (leftover[i].key > 0 && idl_id_is_in_idlist_ranges(idl, idrange_list, leftover[i].key) != 0) {
/* if the leftover key has its parent in the idl */
- idl_rc = idl_append_extend(&idl, leftover[i].id);
- if (idl_rc) {
- slapi_log_err(SLAPI_LOG_ERR, "idl_new_range_fetch",
- "Unable to extend id list (err=%d)\n", idl_rc);
- idl_free(&idl);
- return NULL;
- }
+ idl_append_extend(&idl, leftover[i].id);
+ idrange_add_id(&idrange_list, leftover[i].id);
leftover[i].key = 0;
remaining--;
}
}
}
slapi_ch_free((void **)&leftover);
+ idrange_free(&idrange_list);
}
slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
"Found %d candidates; error code is: %d\n",
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
index 9a7ffee37..f16c37d73 100644
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
@@ -217,6 +217,9 @@ ID idl_firstid(IDList *idl);
ID idl_nextid(IDList *idl, ID id);
int idl_init_private(backend *be, struct attrinfo *a);
int idl_release_private(struct attrinfo *a);
+IdRange_t *idrange_add_id(IdRange_t **head, ID id);
+void idrange_free(IdRange_t **head);
+int idl_id_is_in_idlist_ranges(IDList *idl, IdRange_t *idrange_list, ID id);
int idl_id_is_in_idlist(IDList *idl, ID id);
idl_iterator idl_iterator_init(const IDList *idl);
--
2.52.0

View File

@ -0,0 +1,687 @@
From 46808f90d9026454b77a65bbab7fccdde75253a4 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 11 Jul 2025 11:43:28 +0200
Subject: [PATCH 3/8] Issue 6922 - AddressSanitizer: leaks found by acl test
suite
Description:
Fix multiple memory leaks in ACL plugin:
* aclgroup.c: Fix memory leak in `aclgroup_free()` by properly cleaning
up all user groups before destroying the rwlock and freeing the main
structure
* aclscan.l & acltext.y: use `PERM_FREE` macro instead of `free()`
* Add cleanups in aclparse.c, aclinit.c, acllist.c, acllas.c
Fixes: https://github.com/389ds/389-ds-base/issues/6922
Reviewed by: @progier389 (Thanks!)
---
ldap/servers/plugins/acl/aclgroup.c | 16 ++++-
ldap/servers/plugins/acl/aclinit.c | 4 ++
ldap/servers/plugins/acl/acllas.c | 7 ++
ldap/servers/plugins/acl/acllist.c | 2 +
ldap/servers/plugins/acl/aclparse.c | 7 ++
lib/libaccess/aclscan.l | 104 ++++++++++++++++++++++++++--
lib/libaccess/acltext.y | 54 +++++++--------
7 files changed, 160 insertions(+), 34 deletions(-)
diff --git a/ldap/servers/plugins/acl/aclgroup.c b/ldap/servers/plugins/acl/aclgroup.c
index a5b0426f9..43faeae58 100644
--- a/ldap/servers/plugins/acl/aclgroup.c
+++ b/ldap/servers/plugins/acl/aclgroup.c
@@ -50,8 +50,20 @@ aclgroup_init()
void
aclgroup_free()
{
- slapi_destroy_rwlock(aclUserGroups->aclg_rwlock);
- slapi_ch_free((void **)&aclUserGroups);
+ aclUserGroup *u_group, *next_group;
+
+ if (aclUserGroups) {
+ /* Clean up all user groups */
+ u_group = aclUserGroups->aclg_first;
+ while (u_group) {
+ next_group = u_group->aclug_next;
+ __aclg__delete_userGroup(u_group);
+ u_group = next_group;
+ }
+
+ slapi_destroy_rwlock(aclUserGroups->aclg_rwlock);
+ slapi_ch_free((void **)&aclUserGroups);
+ }
}
/*
diff --git a/ldap/servers/plugins/acl/aclinit.c b/ldap/servers/plugins/acl/aclinit.c
index e2d5eba95..925fbbe53 100644
--- a/ldap/servers/plugins/acl/aclinit.c
+++ b/ldap/servers/plugins/acl/aclinit.c
@@ -409,6 +409,7 @@ __aclinit__RegisterAttributes(void)
rv = ACL_MethodRegister(&errp, DS_METHOD, &methodinfo);
if (rv < 0) {
acl_print_acllib_err(&errp, NULL);
+ nserrDispose(&errp);
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
"__aclinit__RegisterAttributes - Unable to Register the methods\n");
return ACL_ERR;
@@ -416,6 +417,7 @@ __aclinit__RegisterAttributes(void)
rv = ACL_MethodSetDefault(&errp, methodinfo);
if (rv < 0) {
acl_print_acllib_err(&errp, NULL);
+ nserrDispose(&errp);
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
"__aclinit__RegisterAttributes - Unable to Set the default method\n");
return ACL_ERR;
@@ -424,6 +426,7 @@ __aclinit__RegisterAttributes(void)
methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL);
if (rv < 0) {
acl_print_acllib_err(&errp, NULL);
+ nserrDispose(&errp);
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
"__aclinit__RegisterAttributes - Unable to Register Attr ip\n");
return ACL_ERR;
@@ -432,6 +435,7 @@ __aclinit__RegisterAttributes(void)
methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL);
if (rv < 0) {
acl_print_acllib_err(&errp, NULL);
+ nserrDispose(&errp);
slapi_log_err(SLAPI_LOG_ERR, plugin_name,
"__aclinit__RegisterAttributes - Unable to Register Attr dns\n");
return ACL_ERR;
diff --git a/ldap/servers/plugins/acl/acllas.c b/ldap/servers/plugins/acl/acllas.c
index 792216cca..282359ae1 100644
--- a/ldap/servers/plugins/acl/acllas.c
+++ b/ldap/servers/plugins/acl/acllas.c
@@ -258,6 +258,7 @@ DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t auth_in
rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb, subject, resource, auth_info, global_auth);
if (rv != LAS_EVAL_TRUE || (NULL == aclpb)) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"DS_LASIpGetter: Unable to get the ACLPB(%d)\n", rv);
return LAS_EVAL_FAIL;
@@ -334,6 +335,7 @@ DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t auth_i
subject, resource, auth_info, global_auth);
if (rv != LAS_EVAL_TRUE || (NULL == aclpb)) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"DS_LASDnsGetter - Unable to get the ACLPB(%d)\n", rv);
return LAS_EVAL_FAIL;
@@ -3743,6 +3745,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
if (rc != LAS_EVAL_TRUE) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"__acllas_setup - %s:Unable to get the clientdn attribute(%d)\n", lasName, rc);
return LAS_EVAL_FAIL;
@@ -3762,6 +3765,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
if ((rc = PListFindValue(subject, DS_ATTR_ENTRY,
(void **)&linfo->resourceEntry, NULL)) < 0) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"__acllas_setup - %s:Unable to get the Slapi_Entry attr(%d)\n", lasName, rc);
return LAS_EVAL_FAIL;
@@ -3772,6 +3776,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
subject, resource, auth_info, global_auth);
if (rc != LAS_EVAL_TRUE) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"__acllas_setup - %s:Unable to get the ACLPB(%d)\n", lasName, rc);
return LAS_EVAL_FAIL;
@@ -3795,6 +3800,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
if ((rc = PListFindValue(subject, DS_ATTR_AUTHTYPE,
(void **)&linfo->authType, NULL)) < 0) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"__acllas_setup - %s:Unable to get the auth type(%d)\n", lasName, rc);
return LAS_EVAL_FAIL;
@@ -3804,6 +3810,7 @@ __acllas_setup(NSErr_t *errp, char *attr_name, CmpOp_t comparator, int allow_ran
if ((rc = PListFindValue(subject, DS_ATTR_SSF,
(void **)&linfo->ssf, NULL)) < 0) {
acl_print_acllib_err(errp, NULL);
+ nserrDispose(errp);
slapi_log_err(SLAPI_LOG_ACL, plugin_name,
"__acllas_setup - %s:Unable to get the ssf(%d)\n", lasName, rc);
}
diff --git a/ldap/servers/plugins/acl/acllist.c b/ldap/servers/plugins/acl/acllist.c
index bf1168691..6d80a5835 100644
--- a/ldap/servers/plugins/acl/acllist.c
+++ b/ldap/servers/plugins/acl/acllist.c
@@ -526,6 +526,8 @@ acllist_free_aci(aci_t *item)
slapi_sdn_free(&item->aci_sdn);
slapi_filter_free(item->target, 1);
+ slapi_filter_free(item->target_to, 1);
+ slapi_filter_free(item->target_from, 1);
/* slapi_filter_free(item->targetAttr, 1); */
attrArray = item->targetAttr;
diff --git a/ldap/servers/plugins/acl/aclparse.c b/ldap/servers/plugins/acl/aclparse.c
index 261cb69ec..882f4ef28 100644
--- a/ldap/servers/plugins/acl/aclparse.c
+++ b/ldap/servers/plugins/acl/aclparse.c
@@ -423,6 +423,9 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
}
f = slapi_str2filter(tstr);
slapi_ch_free_string(&tstr);
+ if (rv > 0) {
+ slapi_ch_free_string(&tmpstr);
+ }
} else if (strncmp(str, aci_targetdn, targetdnlen) == 0) {
char *tstr = NULL;
size_t LDAP_URL_prefix_len = 0;
@@ -706,6 +709,7 @@ __aclp__sanity_check_acltxt(aci_t *aci_item, char *str)
/* check for acl syntax error */
if ((handle = (ACLListHandle_t *)ACL_ParseString(&errp, newstr)) == NULL) {
acl_print_acllib_err(&errp, str);
+ nserrDispose(&errp);
slapi_ch_free_string(&newstr);
return ACL_SYNTAX_ERR;
} else {
@@ -1144,13 +1148,16 @@ normalize_nextACERule:
if (nextACE && *nextACE != '\0')
aclutil_str_append(&ret_str, nextACE);
slapi_ch_free_string(&s_aclstr);
+ slapi_ch_free_string(&s_acestr);
return (ret_str);
}
acestr = nextACE;
+ slapi_ch_free_string(&s_acestr);
goto normalize_nextACERule;
}
slapi_ch_free_string(&s_aclstr);
+ slapi_ch_free_string(&s_acestr);
return (ret_str);
error:
diff --git a/lib/libaccess/aclscan.l b/lib/libaccess/aclscan.l
index 7067096c0..9c836decc 100644
--- a/lib/libaccess/aclscan.l
+++ b/lib/libaccess/aclscan.l
@@ -64,11 +64,18 @@ variable [\*a-zA-Z0-9\.\-\_][\*a-zA-Z0-9\.\-\_]*
<<EOF>> {
yylval.string = NULL;
- last_string = yylval.string;
+ if (last_string) {
+ PERM_FREE(last_string);
+ last_string = NULL;
+ }
return(0);
}
{qstring} {
+ if (last_string) {
+ PERM_FREE(last_string);
+ last_string = NULL;
+ }
yylval.string = PERM_STRDUP( yytext+1 );
last_string = yylval.string;
if ( yylval.string[yyleng-2] != '"' )
@@ -81,96 +88,144 @@ variable [\*a-zA-Z0-9\.\-\_][\*a-zA-Z0-9\.\-\_]*
absolute {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_ABSOLUTE_TOK;
}
acl {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_ACL_TOK;
}
allow {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_ALLOW_TOK;
}
always {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_ALWAYS_TOK;
}
at {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_AT_TOK;
}
authenticate {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_AUTHENTICATE_TOK;
}
content {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_CONTENT_TOK;
}
default {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_DEFAULT_TOK;
}
deny {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_DENY_TOK;
}
in {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_IN_TOK;
}
inherit {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_INHERIT_TOK;
}
terminal {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_TERMINAL_TOK;
}
version {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_VERSION_TOK;
}
with {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_WITH_TOK;
}
not {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return ACL_NOT_TOK;
}
and {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = ACL_EXPR_OP_AND;
acl_tokenpos += yyleng;
@@ -178,6 +233,9 @@ and {
}
or {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = ACL_EXPR_OP_OR;
acl_tokenpos += yyleng;
@@ -185,6 +243,9 @@ or {
}
"=" {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = CMP_OP_EQ;
acl_tokenpos += yyleng;
@@ -192,6 +253,9 @@ or {
}
">=" {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = CMP_OP_GE;
acl_tokenpos += yyleng;
@@ -199,6 +263,9 @@ or {
}
">" {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = CMP_OP_GT;
acl_tokenpos += yyleng;
@@ -206,6 +273,9 @@ or {
}
"<" {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = CMP_OP_LT;
acl_tokenpos += yyleng;
@@ -213,6 +283,9 @@ or {
}
"<=" {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = CMP_OP_LE;
acl_tokenpos += yyleng;
@@ -220,6 +293,9 @@ or {
}
"!=" {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
yylval.ival = CMP_OP_NE;
acl_tokenpos += yyleng;
@@ -227,6 +303,9 @@ or {
}
[(){},;] {
+ if (last_string) {
+ PERM_FREE(last_string);
+ }
last_string = NULL;
acl_tokenpos += yyleng;
return yytext[0];
@@ -234,6 +313,10 @@ or {
{variable} {
acl_tokenpos += yyleng;
+ if (last_string) {
+ PERM_FREE(last_string);
+ last_string = NULL;
+ }
yylval.string = PERM_STRDUP( yytext );
last_string = yylval.string;
return ACL_VARIABLE_TOK;
@@ -272,8 +355,10 @@ char errorStr[256];
#if defined(UTEST) || defined(ACL_COMPILER)
printf("ACL file: %s\n", acl_filename);
printf("Syntax error at line: %d, token: %s\n", acl_lineno, yytext);
- if ( last_string )
- free(last_string);
+ if ( last_string ) {
+ PERM_FREE(last_string);
+ last_string = NULL;
+ }
#else
sprintf(errorStr, "%d", acl_lineno);
if (yytext) {
@@ -283,8 +368,10 @@ char errorStr[256];
nserrGenerate(acl_errp, ACLERRPARSE, ACLERR1780, ACL_Program,
2, acl_filename, errorStr);
}
- if ( last_string )
- free(last_string);
+ if ( last_string ) {
+ PERM_FREE(last_string);
+ last_string = NULL;
+ }
#endif
}
@@ -294,6 +381,7 @@ acl_InitScanner(NSErr_t *errp, char *filename, char *buffer)
{
acl_errp = errp;
acl_lineno = 1;
+ last_string = NULL;
acl_use_buffer = (filename == NULL) ? 1 : 0 ;
if ( filename != NULL ) {
PL_strncpyz(acl_filename, filename, sizeof(acl_filename));
@@ -342,6 +430,12 @@ acl_EndScanner()
#endif
yyin = NULL ;
}
+
+ if ( last_string ) {
+ PERM_FREE(last_string);
+ last_string = NULL;
+ }
+
return(0);
}
diff --git a/lib/libaccess/acltext.y b/lib/libaccess/acltext.y
index 16eea355b..ed2c6109b 100644
--- a/lib/libaccess/acltext.y
+++ b/lib/libaccess/acltext.y
@@ -80,7 +80,7 @@ acl_free_args(char **args_list)
for (ii = 0; ii < MAX_LIST_SIZE; ii++) {
if ( args_list[ii] )
- free(args_list[ii]);
+ PERM_FREE(args_list[ii]);
else
break;
}
@@ -240,7 +240,7 @@ acl_set_ip_dns(ACLExprHandle_t *expr, char **ip_dns)
start: | start_acl_v2
| ACL_VERSION_TOK ACL_VARIABLE_TOK
{
- free($<string>2);
+ PERM_FREE($<string>2);
}
';' start_acl_v3
;
@@ -267,7 +267,7 @@ acl_v2: ACL_ACL_TOK acl_name_v2
acl_name_v2: ACL_VARIABLE_TOK
{
curr_acl = ACL_AclNew(NULL, $<string>1);
- free($<string>1);
+ PERM_FREE($<string>1);
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
yyerror("Couldn't add ACL to list.");
return(-1);
@@ -289,7 +289,7 @@ acl_name_v2: ACL_VARIABLE_TOK
| ACL_QSTRING_TOK
{
curr_acl = ACL_AclNew(NULL, $<string>1);
- free($<string>1);
+ PERM_FREE($<string>1);
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
yyerror("Couldn't add ACL to list.");
return(-1);
@@ -503,8 +503,8 @@ ip_spec_v2: ACL_VARIABLE_TOK ACL_VARIABLE_TOK
char tmp_str[255];
util_sprintf(tmp_str, "%s+%s", $<string>1, $<string>2);
- free($<string>1);
- free($<string>2);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>2);
acl_add_arg(curr_ip_dns_list, PERM_STRDUP(tmp_str));
}
;
@@ -538,26 +538,26 @@ method_v2: ACL_VARIABLE_TOK ACL_VARIABLE_TOK ';'
{
acl_string_lower($<string>1);
if (strcmp($<string>1, "database") == 0) {
- free($<string>1);
- free($<string>2);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>2);
} else {
if ( PListInitProp(curr_auth_info,
ACL_Attr2Index($<string>1), $<string>1, $<string>2, NULL) < 0 ) {
}
- free($<string>1);
+ PERM_FREE($<string>1);
}
}
| ACL_VARIABLE_TOK ACL_QSTRING_TOK ';'
{
acl_string_lower($<string>1);
if (strcmp($<string>1, "database") == 0) {
- free($<string>1);
- free($<string>2);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>2);
} else {
if ( PListInitProp(curr_auth_info,
ACL_Attr2Index($<string>1), $<string>1, $<string>2, NULL) < 0 ) {
}
- free($<string>1);
+ PERM_FREE($<string>1);
}
}
;
@@ -586,7 +586,7 @@ acl: named_acl ';' body_list
named_acl: ACL_ACL_TOK ACL_VARIABLE_TOK
{
curr_acl = ACL_AclNew(NULL, $<string>2);
- free($<string>2);
+ PERM_FREE($<string>2);
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
yyerror("Couldn't add ACL to list.");
return(-1);
@@ -595,7 +595,7 @@ named_acl: ACL_ACL_TOK ACL_VARIABLE_TOK
| ACL_ACL_TOK ACL_QSTRING_TOK
{
curr_acl = ACL_AclNew(NULL, $<string>2);
- free($<string>2);
+ PERM_FREE($<string>2);
if ( ACL_ListAppend(NULL, curr_acl_list, curr_acl, 0) < 0 ) {
yyerror("Couldn't add ACL to list.");
return(-1);
@@ -654,8 +654,8 @@ deny_common: ACL_VARIABLE_TOK ACL_EQ_TOK ACL_QSTRING_TOK
yyerror("ACL_ExprSetDenyWith() failed");
return(-1);
}
- free($<string>1);
- free($<string>3);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>3);
}
;
@@ -692,7 +692,7 @@ attribute: ACL_VARIABLE_TOK
yyerror("ACL_ExprAddArg() failed");
return(-1);
}
- free($<string>1);
+ PERM_FREE($<string>1);
}
;
@@ -706,7 +706,7 @@ parameter: ACL_VARIABLE_TOK ACL_EQ_TOK ACL_QSTRING_TOK
if ( PListInitProp(curr_auth_info,
ACL_Attr2Index($<string>1), $<string>1, $<string>3, NULL) < 0 ) {
}
- free($<string>1);
+ PERM_FREE($<string>1);
}
| ACL_VARIABLE_TOK ACL_EQ_TOK ACL_VARIABLE_TOK
{
@@ -714,7 +714,7 @@ parameter: ACL_VARIABLE_TOK ACL_EQ_TOK ACL_QSTRING_TOK
if ( PListInitProp(curr_auth_info,
ACL_Attr2Index($<string>1), $<string>1, $<string>3, NULL) < 0 ) {
}
- free($<string>1);
+ PERM_FREE($<string>1);
}
;
@@ -896,12 +896,12 @@ base_expr: ACL_VARIABLE_TOK relop ACL_QSTRING_TOK
if ( ACL_ExprTerm(NULL, curr_expr,
$<string>1, (CmpOp_t) $<ival>2, $<string>3) < 0 ) {
yyerror("ACL_ExprTerm() failed");
- free($<string>1);
- free($<string>3);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>3);
return(-1);
}
- free($<string>1);
- free($<string>3);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>3);
}
| ACL_VARIABLE_TOK relop ACL_VARIABLE_TOK
{
@@ -909,12 +909,12 @@ base_expr: ACL_VARIABLE_TOK relop ACL_QSTRING_TOK
if ( ACL_ExprTerm(NULL, curr_expr,
$<string>1, (CmpOp_t) $<ival>2, $<string>3) < 0 ) {
yyerror("ACL_ExprTerm() failed");
- free($<string>1);
- free($<string>3);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>3);
return(-1);
}
- free($<string>1);
- free($<string>3);
+ PERM_FREE($<string>1);
+ PERM_FREE($<string>3);
}
;
--
2.54.0

View File

@ -1,765 +0,0 @@
From a0af5ab79ddad7944687e31f16bb2a667c800958 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 7 Jan 2026 16:55:27 -0500
Subject: [PATCH] Issue - Revise paged result search locking
Description:
Move to a single lock approach verses having two locks. This will impact
concurrency when multiple async paged result searches are done on the same
connection, but it simplifies the code and avoids race conditions and
deadlocks.
Relates: https://github.com/389ds/389-ds-base/issues/7118
Reviewed by: progier & tbordaz (Thanks!!)
---
ldap/servers/slapd/abandon.c | 2 +-
ldap/servers/slapd/opshared.c | 60 ++++----
ldap/servers/slapd/pagedresults.c | 228 +++++++++++++++++++-----------
ldap/servers/slapd/proto-slap.h | 26 ++--
ldap/servers/slapd/slap.h | 5 +-
5 files changed, 187 insertions(+), 134 deletions(-)
diff --git a/ldap/servers/slapd/abandon.c b/ldap/servers/slapd/abandon.c
index 2dd1ee320..d7c1f8868 100644
--- a/ldap/servers/slapd/abandon.c
+++ b/ldap/servers/slapd/abandon.c
@@ -141,7 +141,7 @@ do_abandon(Slapi_PBlock *pb)
}
pthread_mutex_unlock(&(pb_conn->c_mutex));
- if (0 == pagedresults_free_one_msgid(pb_conn, id, pageresult_lock_get_addr(pb_conn))) {
+ if (0 == pagedresults_free_one_msgid(pb_conn, id, PR_NOT_LOCKED)) {
slapi_log_access(LDAP_DEBUG_STATS, "conn=%" PRIu64
" op=%d ABANDON targetop=Simple Paged Results msgid=%d\n",
pb_conn->c_connid, pb_op->o_opid, id);
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
index 03ed60981..bb474ef3d 100644
--- a/ldap/servers/slapd/opshared.c
+++ b/ldap/servers/slapd/opshared.c
@@ -545,8 +545,8 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
be = be_list[index];
}
}
- pr_search_result = pagedresults_get_search_result(pb_conn, operation, 0 /*not locked*/, pr_idx);
- estimate = pagedresults_get_search_result_set_size_estimate(pb_conn, operation, pr_idx);
+ pr_search_result = pagedresults_get_search_result(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
+ estimate = pagedresults_get_search_result_set_size_estimate(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
/* Set operation note flags as required. */
if (pagedresults_get_unindexed(pb_conn, operation, pr_idx)) {
slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_UNINDEXED);
@@ -592,14 +592,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
int32_t tlimit;
slapi_pblock_get(pb, SLAPI_SEARCH_TIMELIMIT, &tlimit);
pagedresults_set_timelimit(pb_conn, operation, (time_t)tlimit, pr_idx);
- /* When using this mutex in conjunction with the main paged
- * result lock, you must do so in this order:
- *
- * --> pagedresults_lock()
- * --> pagedresults_mutex
- * <-- pagedresults_mutex
- * <-- pagedresults_unlock()
- */
+ /* IMPORTANT: Never acquire pagedresults_mutex when holding c_mutex. */
pagedresults_mutex = pageresult_lock_get_addr(pb_conn);
}
@@ -716,17 +709,15 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
if (op_is_pagedresults(operation) && pr_search_result) {
void *sr = NULL;
/* PAGED RESULTS and already have the search results from the prev op */
- pagedresults_lock(pb_conn, pr_idx);
/*
* In async paged result case, the search result might be released
* by other theads. We need to double check it in the locked region.
*/
pthread_mutex_lock(pagedresults_mutex);
- pr_search_result = pagedresults_get_search_result(pb_conn, operation, 1 /*locked*/, pr_idx);
+ pr_search_result = pagedresults_get_search_result(pb_conn, operation, PR_LOCKED, pr_idx);
if (pr_search_result) {
- if (pagedresults_is_abandoned_or_notavailable(pb_conn, 1 /*locked*/, pr_idx)) {
+ if (pagedresults_is_abandoned_or_notavailable(pb_conn, PR_LOCKED, pr_idx)) {
pthread_mutex_unlock(pagedresults_mutex);
- pagedresults_unlock(pb_conn, pr_idx);
/* Previous operation was abandoned and the simplepaged object is not in use. */
send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
rc = LDAP_SUCCESS;
@@ -737,14 +728,13 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
/* search result could be reset in the backend/dse */
slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr);
- pagedresults_set_search_result(pb_conn, operation, sr, 1 /*locked*/, pr_idx);
+ pagedresults_set_search_result(pb_conn, operation, sr, PR_LOCKED, pr_idx);
}
} else {
pr_stat = PAGEDRESULTS_SEARCH_END;
rc = LDAP_SUCCESS;
}
pthread_mutex_unlock(pagedresults_mutex);
- pagedresults_unlock(pb_conn, pr_idx);
if ((PAGEDRESULTS_SEARCH_END == pr_stat) || (0 == pnentries)) {
/* no more entries to send in the backend */
@@ -762,22 +752,22 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
}
pagedresults_set_response_control(pb, 0, estimate,
curr_search_count, pr_idx);
- if (pagedresults_get_with_sort(pb_conn, operation, pr_idx)) {
+ if (pagedresults_get_with_sort(pb_conn, operation, PR_NOT_LOCKED, pr_idx)) {
sort_make_sort_response_control(pb, CONN_GET_SORT_RESULT_CODE, NULL);
}
pagedresults_set_search_result_set_size_estimate(pb_conn,
operation,
- estimate, pr_idx);
+ estimate, PR_NOT_LOCKED, pr_idx);
if (PAGEDRESULTS_SEARCH_END == pr_stat) {
- pagedresults_lock(pb_conn, pr_idx);
+ pthread_mutex_lock(pagedresults_mutex);
slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
- if (!pagedresults_is_abandoned_or_notavailable(pb_conn, 0 /*not locked*/, pr_idx)) {
- pagedresults_free_one(pb_conn, operation, pr_idx);
+ if (!pagedresults_is_abandoned_or_notavailable(pb_conn, PR_LOCKED, pr_idx)) {
+ pagedresults_free_one(pb_conn, operation, PR_LOCKED, pr_idx);
}
- pagedresults_unlock(pb_conn, pr_idx);
+ pthread_mutex_unlock(pagedresults_mutex);
if (next_be) {
/* no more entries, but at least another backend */
- if (pagedresults_set_current_be(pb_conn, next_be, pr_idx, 0) < 0) {
+ if (pagedresults_set_current_be(pb_conn, next_be, pr_idx, PR_NOT_LOCKED) < 0) {
goto free_and_return;
}
}
@@ -884,7 +874,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
}
}
pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
- rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1);
+ rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, PR_LOCKED);
pthread_mutex_unlock(pagedresults_mutex);
}
@@ -922,7 +912,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
pthread_mutex_lock(pagedresults_mutex);
pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
be->be_search_results_release(&sr);
- rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, 1);
+ rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, PR_LOCKED);
pthread_mutex_unlock(pagedresults_mutex);
pr_stat = PAGEDRESULTS_SEARCH_END; /* make sure stat is SEARCH_END */
if (NULL == next_be) {
@@ -935,23 +925,23 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
} else {
curr_search_count = pnentries;
slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET_SIZE_ESTIMATE, &estimate);
- pagedresults_lock(pb_conn, pr_idx);
- if ((pagedresults_set_current_be(pb_conn, be, pr_idx, 0) < 0) ||
- (pagedresults_set_search_result(pb_conn, operation, sr, 0, pr_idx) < 0) ||
- (pagedresults_set_search_result_count(pb_conn, operation, curr_search_count, pr_idx) < 0) ||
- (pagedresults_set_search_result_set_size_estimate(pb_conn, operation, estimate, pr_idx) < 0) ||
- (pagedresults_set_with_sort(pb_conn, operation, with_sort, pr_idx) < 0)) {
- pagedresults_unlock(pb_conn, pr_idx);
+ pthread_mutex_lock(pagedresults_mutex);
+ if ((pagedresults_set_current_be(pb_conn, be, pr_idx, PR_LOCKED) < 0) ||
+ (pagedresults_set_search_result(pb_conn, operation, sr, PR_LOCKED, pr_idx) < 0) ||
+ (pagedresults_set_search_result_count(pb_conn, operation, curr_search_count, PR_LOCKED, pr_idx) < 0) ||
+ (pagedresults_set_search_result_set_size_estimate(pb_conn, operation, estimate, PR_LOCKED, pr_idx) < 0) ||
+ (pagedresults_set_with_sort(pb_conn, operation, with_sort, PR_LOCKED, pr_idx) < 0)) {
+ pthread_mutex_unlock(pagedresults_mutex);
cache_return_target_entry(pb, be, operation);
goto free_and_return;
}
- pagedresults_unlock(pb_conn, pr_idx);
+ pthread_mutex_unlock(pagedresults_mutex);
}
slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
next_be = NULL; /* to break the loop */
if (operation->o_status & SLAPI_OP_STATUS_ABANDONED) {
/* It turned out this search was abandoned. */
- pagedresults_free_one_msgid(pb_conn, operation->o_msgid, pagedresults_mutex);
+ pagedresults_free_one_msgid(pb_conn, operation->o_msgid, PR_NOT_LOCKED);
/* paged-results-request was abandoned; making an empty cookie. */
pagedresults_set_response_control(pb, 0, estimate, -1, pr_idx);
send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
@@ -961,7 +951,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
}
pagedresults_set_response_control(pb, 0, estimate, curr_search_count, pr_idx);
if (curr_search_count == -1) {
- pagedresults_free_one(pb_conn, operation, pr_idx);
+ pagedresults_free_one(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
}
}
diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
index 941ab97e3..0d6c4a1aa 100644
--- a/ldap/servers/slapd/pagedresults.c
+++ b/ldap/servers/slapd/pagedresults.c
@@ -34,9 +34,9 @@ pageresult_lock_cleanup()
slapi_ch_free((void**)&lock_hash);
}
-/* Beware to the lock order with c_mutex:
- * c_mutex is sometime locked while holding pageresult_lock
- * ==> Do not lock pageresult_lock when holing c_mutex
+/* Lock ordering constraint with c_mutex:
+ * c_mutex is sometimes locked while holding pageresult_lock.
+ * Therefore: DO NOT acquire pageresult_lock when holding c_mutex.
*/
pthread_mutex_t *
pageresult_lock_get_addr(Connection *conn)
@@ -44,7 +44,11 @@ pageresult_lock_get_addr(Connection *conn)
return &lock_hash[(((size_t)conn)/sizeof (Connection))%LOCK_HASH_SIZE];
}
-/* helper function to clean up one prp slot */
+/* helper function to clean up one prp slot
+ *
+ * NOTE: This function must be called while holding the pageresult_lock
+ * (via pageresult_lock_get_addr(conn)) to ensure thread-safe cleanup.
+ */
static void
_pr_cleanup_one_slot(PagedResults *prp)
{
@@ -56,7 +60,7 @@ _pr_cleanup_one_slot(PagedResults *prp)
prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
}
- /* clean up the slot except the mutex */
+ /* clean up the slot */
prp->pr_current_be = NULL;
prp->pr_search_result_set = NULL;
prp->pr_search_result_count = 0;
@@ -136,6 +140,8 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
return LDAP_UNWILLING_TO_PERFORM;
}
+ /* Acquire hash-based lock for paged results list access
+ * IMPORTANT: Never acquire this lock when holding c_mutex */
pthread_mutex_lock(pageresult_lock_get_addr(conn));
/* the ber encoding is no longer needed */
ber_free(ber, 1);
@@ -184,10 +190,6 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
goto bail;
}
- if ((*index > -1) && (*index < conn->c_pagedresults.prl_maxlen) &&
- !conn->c_pagedresults.prl_list[*index].pr_mutex) {
- conn->c_pagedresults.prl_list[*index].pr_mutex = PR_NewLock();
- }
conn->c_pagedresults.prl_count++;
} else {
/* Repeated paged results request.
@@ -327,8 +329,14 @@ bailout:
"<= idx=%d\n", index);
}
+/*
+ * Free one paged result entry by index.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_free_one(Connection *conn, Operation *op, int index)
+pagedresults_free_one(Connection *conn, Operation *op, bool locked, int index)
{
int rc = -1;
@@ -338,7 +346,9 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one",
"=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (conn->c_pagedresults.prl_count <= 0) {
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one",
"conn=%" PRIu64 " paged requests list count is %d\n",
@@ -349,7 +359,9 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
conn->c_pagedresults.prl_count--;
rc = 0;
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
}
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one", "<= %d\n", rc);
@@ -357,21 +369,28 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
}
/*
- * Used for abandoning - pageresult_lock_get_addr(conn) is already locked in do_abandone.
+ * Free one paged result entry by message ID.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
*/
int
-pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex)
+pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, bool locked)
{
int rc = -1;
int i;
+ pthread_mutex_t *lock = NULL;
if (conn && (msgid > -1)) {
if (conn->c_pagedresults.prl_maxlen <= 0) {
; /* Not a paged result. */
} else {
slapi_log_err(SLAPI_LOG_TRACE,
- "pagedresults_free_one_msgid_nolock", "=> msgid=%d\n", msgid);
- pthread_mutex_lock(mutex);
+ "pagedresults_free_one_msgid", "=> msgid=%d\n", msgid);
+ lock = pageresult_lock_get_addr(conn);
+ if (!locked) {
+ pthread_mutex_lock(lock);
+ }
for (i = 0; i < conn->c_pagedresults.prl_maxlen; i++) {
if (conn->c_pagedresults.prl_list[i].pr_msgid == msgid) {
PagedResults *prp = conn->c_pagedresults.prl_list + i;
@@ -390,9 +409,11 @@ pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *
break;
}
}
- pthread_mutex_unlock(mutex);
+ if (!locked) {
+ pthread_mutex_unlock(lock);
+ }
slapi_log_err(SLAPI_LOG_TRACE,
- "pagedresults_free_one_msgid_nolock", "<= %d\n", rc);
+ "pagedresults_free_one_msgid", "<= %d\n", rc);
}
}
@@ -418,29 +439,43 @@ pagedresults_get_current_be(Connection *conn, int index)
return be;
}
+/*
+ * Set current backend for a paged result entry.
+ *
+ * Locking: If locked=false, acquires pageresult_lock. If locked=true, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int nolock)
+pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, bool locked)
{
int rc = -1;
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_set_current_be", "=> idx=%d\n", index);
if (conn && (index > -1)) {
- if (!nolock)
+ if (!locked) {
pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
conn->c_pagedresults.prl_list[index].pr_current_be = be;
}
rc = 0;
- if (!nolock)
+ if (!locked) {
pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
}
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_set_current_be", "<= %d\n", rc);
return rc;
}
+/*
+ * Get search result set for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
void *
-pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int index)
+pagedresults_get_search_result(Connection *conn, Operation *op, bool locked, int index)
{
void *sr = NULL;
if (!op_is_pagedresults(op)) {
@@ -465,8 +500,14 @@ pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int
return sr;
}
+/*
+ * Set search result set for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int locked, int index)
+pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, bool locked, int index)
{
int rc = -1;
if (!op_is_pagedresults(op)) {
@@ -494,8 +535,14 @@ pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int lo
return rc;
}
+/*
+ * Get search result count for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_get_search_result_count(Connection *conn, Operation *op, int index)
+pagedresults_get_search_result_count(Connection *conn, Operation *op, bool locked, int index)
{
int count = 0;
if (!op_is_pagedresults(op)) {
@@ -504,19 +551,29 @@ pagedresults_get_search_result_count(Connection *conn, Operation *op, int index)
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_get_search_result_count", "=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
count = conn->c_pagedresults.prl_list[index].pr_search_result_count;
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
}
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_get_search_result_count", "<= %d\n", count);
return count;
}
+/*
+ * Set search result count for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, int index)
+pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, bool locked, int index)
{
int rc = -1;
if (!op_is_pagedresults(op)) {
@@ -525,11 +582,15 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count,
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_set_search_result_count", "=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
conn->c_pagedresults.prl_list[index].pr_search_result_count = count;
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
rc = 0;
}
slapi_log_err(SLAPI_LOG_TRACE,
@@ -537,9 +598,16 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count,
return rc;
}
+/*
+ * Get search result set size estimate for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
pagedresults_get_search_result_set_size_estimate(Connection *conn,
Operation *op,
+ bool locked,
int index)
{
int count = 0;
@@ -550,11 +618,15 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn,
"pagedresults_get_search_result_set_size_estimate",
"=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
count = conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate;
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
}
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_get_search_result_set_size_estimate", "<= %d\n",
@@ -562,10 +634,17 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn,
return count;
}
+/*
+ * Set search result set size estimate for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
pagedresults_set_search_result_set_size_estimate(Connection *conn,
Operation *op,
int count,
+ bool locked,
int index)
{
int rc = -1;
@@ -576,11 +655,15 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn,
"pagedresults_set_search_result_set_size_estimate",
"=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate = count;
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
rc = 0;
}
slapi_log_err(SLAPI_LOG_TRACE,
@@ -589,8 +672,14 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn,
return rc;
}
+/*
+ * Get with_sort flag for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_get_with_sort(Connection *conn, Operation *op, int index)
+pagedresults_get_with_sort(Connection *conn, Operation *op, bool locked, int index)
{
int flags = 0;
if (!op_is_pagedresults(op)) {
@@ -599,19 +688,29 @@ pagedresults_get_with_sort(Connection *conn, Operation *op, int index)
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_get_with_sort", "=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
flags = conn->c_pagedresults.prl_list[index].pr_flags & CONN_FLAG_PAGEDRESULTS_WITH_SORT;
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
}
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_get_with_sort", "<= %d\n", flags);
return flags;
}
+/*
+ * Set with_sort flag for a paged result entry.
+ *
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
+ */
int
-pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index)
+pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, bool locked, int index)
{
int rc = -1;
if (!op_is_pagedresults(op)) {
@@ -620,14 +719,18 @@ pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index
slapi_log_err(SLAPI_LOG_TRACE,
"pagedresults_set_with_sort", "=> idx=%d\n", index);
if (conn && (index > -1)) {
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
+ }
if (index < conn->c_pagedresults.prl_maxlen) {
if (flags & OP_FLAG_SERVER_SIDE_SORTING) {
conn->c_pagedresults.prl_list[index].pr_flags |=
CONN_FLAG_PAGEDRESULTS_WITH_SORT;
}
}
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ if (!locked) {
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
+ }
rc = 0;
}
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_set_with_sort", "<= %d\n", rc);
@@ -802,10 +905,6 @@ pagedresults_cleanup(Connection *conn, int needlock)
rc = 1;
}
prp->pr_current_be = NULL;
- if (prp->pr_mutex) {
- PR_DestroyLock(prp->pr_mutex);
- prp->pr_mutex = NULL;
- }
memset(prp, '\0', sizeof(PagedResults));
}
conn->c_pagedresults.prl_count = 0;
@@ -840,10 +939,6 @@ pagedresults_cleanup_all(Connection *conn, int needlock)
i < conn->c_pagedresults.prl_maxlen;
i++) {
prp = conn->c_pagedresults.prl_list + i;
- if (prp->pr_mutex) {
- PR_DestroyLock(prp->pr_mutex);
- prp->pr_mutex = NULL;
- }
if (prp->pr_current_be && prp->pr_search_result_set &&
prp->pr_current_be->be_search_results_release) {
prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
@@ -1010,43 +1105,8 @@ op_set_pagedresults(Operation *op)
op->o_flags |= OP_FLAG_PAGED_RESULTS;
}
-/*
- * pagedresults_lock/unlock -- introduced to protect search results for the
- * asynchronous searches. Do not call these functions while the PR conn lock
- * is held (e.g. pageresult_lock_get_addr(conn))
- */
-void
-pagedresults_lock(Connection *conn, int index)
-{
- PagedResults *prp;
- if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
- return;
- }
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
- prp = conn->c_pagedresults.prl_list + index;
- if (prp->pr_mutex) {
- PR_Lock(prp->pr_mutex);
- }
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-}
-
-void
-pagedresults_unlock(Connection *conn, int index)
-{
- PagedResults *prp;
- if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
- return;
- }
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
- prp = conn->c_pagedresults.prl_list + index;
- if (prp->pr_mutex) {
- PR_Unlock(prp->pr_mutex);
- }
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
-}
-
int
-pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index)
+pagedresults_is_abandoned_or_notavailable(Connection *conn, bool locked, int index)
{
PagedResults *prp;
int32_t result;
@@ -1066,7 +1126,7 @@ pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int inde
}
int
-pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked)
+pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, bool locked)
{
int rc = -1;
Connection *conn = NULL;
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 37c643ea9..742e3ee14 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -1597,20 +1597,22 @@ pthread_mutex_t *pageresult_lock_get_addr(Connection *conn);
int pagedresults_parse_control_value(Slapi_PBlock *pb, struct berval *psbvp, ber_int_t *pagesize, int *index, Slapi_Backend *be);
void pagedresults_set_response_control(Slapi_PBlock *pb, int iscritical, ber_int_t estimate, int curr_search_count, int index);
Slapi_Backend *pagedresults_get_current_be(Connection *conn, int index);
-int pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int nolock);
-void *pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int index);
-int pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int locked, int index);
-int pagedresults_get_search_result_count(Connection *conn, Operation *op, int index);
-int pagedresults_set_search_result_count(Connection *conn, Operation *op, int cnt, int index);
+int pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, bool locked);
+void *pagedresults_get_search_result(Connection *conn, Operation *op, bool locked, int index);
+int pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, bool locked, int index);
+int pagedresults_get_search_result_count(Connection *conn, Operation *op, bool locked, int index);
+int pagedresults_set_search_result_count(Connection *conn, Operation *op, int cnt, bool locked, int index);
int pagedresults_get_search_result_set_size_estimate(Connection *conn,
Operation *op,
+ bool locked,
int index);
int pagedresults_set_search_result_set_size_estimate(Connection *conn,
Operation *op,
int cnt,
+ bool locked,
int index);
-int pagedresults_get_with_sort(Connection *conn, Operation *op, int index);
-int pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index);
+int pagedresults_get_with_sort(Connection *conn, Operation *op, bool locked, int index);
+int pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, bool locked, int index);
int pagedresults_get_unindexed(Connection *conn, Operation *op, int index);
int pagedresults_set_unindexed(Connection *conn, Operation *op, int index);
int pagedresults_get_sort_result_code(Connection *conn, Operation *op, int index);
@@ -1622,15 +1624,13 @@ int pagedresults_cleanup(Connection *conn, int needlock);
int pagedresults_is_timedout_nolock(Connection *conn);
int pagedresults_reset_timedout_nolock(Connection *conn);
int pagedresults_in_use_nolock(Connection *conn);
-int pagedresults_free_one(Connection *conn, Operation *op, int index);
-int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex);
+int pagedresults_free_one(Connection *conn, Operation *op, bool locked, int index);
+int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, bool locked);
int op_is_pagedresults(Operation *op);
int pagedresults_cleanup_all(Connection *conn, int needlock);
void op_set_pagedresults(Operation *op);
-void pagedresults_lock(Connection *conn, int index);
-void pagedresults_unlock(Connection *conn, int index);
-int pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index);
-int pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked);
+int pagedresults_is_abandoned_or_notavailable(Connection *conn, bool locked, int index);
+int pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, bool locked);
/*
* sort.c
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index 9aaa4fb80..f2395cfa2 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -80,6 +80,10 @@ static char ptokPBE[34] = "Internal (Software) Token ";
#include <stdbool.h>
#include <time.h> /* For timespec definitions */
+/* Macros for paged results lock parameter */
+#define PR_LOCKED true
+#define PR_NOT_LOCKED false
+
/* Provides our int types and platform specific requirements. */
#include <slapi_pal.h>
@@ -1656,7 +1660,6 @@ typedef struct _paged_results
struct timespec pr_timelimit_hr; /* expiry time of this request rel to clock monotonic */
int pr_flags;
ber_int_t pr_msgid; /* msgid of the request; to abandon */
- PRLock *pr_mutex; /* protect each conn structure */
} PagedResults;
/* array of simple paged structure stashed in connection */
--
2.52.0

View File

@ -1,213 +0,0 @@
From 5b6211de252f801ffc088703acc242e47f6273ab Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 9 Jan 2026 11:39:50 +0100
Subject: [PATCH] Issue 7172 - Index ordering mismatch after upgrade (#7173)
Bug Description:
Commit daf731f55071d45eaf403a52b63d35f4e699ff28 introduced a regression.
After upgrading to a version that adds `integerOrderingMatch` matching
rule to `parentid` and `ancestorid` indexes, searches may return empty
or incorrect results.
This happens because the existing index data was created with
lexicographic ordering, but the new compare function expects integer
ordering. Index lookups fail because the compare function doesn't match
the data ordering.
The root cause is that `ldbm_instance_create_default_indexes()` calls
`attr_index_config()` unconditionally for `parentid` and `ancestorid`
indexes, which triggers `ainfo_dup()` to overwrite `ai_key_cmp_fn` on
existing indexes. This breaks indexes that were created without the
`integerOrderingMatch` matching rule.
Fix Description:
* Call `attr_index_config()` for `parentid` and `ancestorid` indexes
only if index config doesn't exist.
* Add `upgrade_check_id_index_matching_rule()` that logs an error on
server startup if `parentid` or `ancestorid` indexes are missing the
integerOrderingMatch matching rule, advising administrators to reindex.
Fixes: https://github.com/389ds/389-ds-base/issues/7172
Reviewed by: @tbordaz, @progier389, @droideck (Thanks!)
---
ldap/servers/slapd/back-ldbm/instance.c | 25 ++++--
ldap/servers/slapd/upgrade.c | 105 ++++++++++++++++++++++++
2 files changed, 122 insertions(+), 8 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
index 24c00200b..30db99c81 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -191,6 +191,7 @@ ldbm_instance_create_default_indexes(backend *be)
char *ancestorid_indexes_limit = NULL;
char *parentid_indexes_limit = NULL;
struct attrinfo *ai = NULL;
+ struct attrinfo *index_already_configured = NULL;
struct index_idlistsizeinfo *iter;
int cookie;
int limit;
@@ -255,10 +256,14 @@ ldbm_instance_create_default_indexes(backend *be)
slapi_entry_free(e);
}
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
- ldbm_instance_config_add_index_entry(inst, e, flags);
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
- slapi_entry_free(e);
+ ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
+ index_already_configured = ai;
+ if (!index_already_configured) {
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
+ ldbm_instance_config_add_index_entry(inst, e, flags);
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
+ slapi_entry_free(e);
+ }
e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
@@ -296,10 +301,14 @@ ldbm_instance_create_default_indexes(backend *be)
* ancestorid is special, there is actually no such attr type
* but we still want to use the attr index file APIs.
*/
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
- ldbm_instance_config_add_index_entry(inst, e, flags);
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
- slapi_entry_free(e);
+ ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
+ index_already_configured = ai;
+ if (!index_already_configured) {
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
+ ldbm_instance_config_add_index_entry(inst, e, flags);
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
+ slapi_entry_free(e);
+ }
}
slapi_ch_free_string(&ancestorid_indexes_limit);
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
index 0d1e2abf4..43906c1af 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -279,6 +279,107 @@ upgrade_205_fixup_repl_dep(void)
return UPGRADE_SUCCESS;
}
+/*
+ * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
+ * matching rule.
+ *
+ * This function logs a warning if we detect this condition, advising
+ * the administrator to reindex the affected attributes.
+ */
+static upgrade_status
+upgrade_check_id_index_matching_rule(void)
+{
+ struct slapi_pblock *pb = slapi_pblock_new();
+ Slapi_Entry **backends = NULL;
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
+ const char *be_filter = "(objectclass=nsBackendInstance)";
+ const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
+ upgrade_status uresult = UPGRADE_SUCCESS;
+
+ /* Search for all backend instances */
+ slapi_search_internal_set_pb(
+ pb, be_base_dn,
+ LDAP_SCOPE_ONELEVEL,
+ be_filter, NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
+
+ if (backends) {
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
+ if (!be_name) {
+ continue;
+ }
+
+ /* Check each attribute that should have integerOrderingMatch */
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
+ const char *attr_name = attrs_to_check[attr_idx];
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
+ Slapi_Entry **idx_entries = NULL;
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
+ attr_name, be_name, be_base_dn);
+ char *idx_filter = "(objectclass=nsIndex)";
+ PRBool has_matching_rule = PR_FALSE;
+
+ if (!idx_dn) {
+ slapi_pblock_destroy(idx_pb);
+ continue;
+ }
+
+ slapi_search_internal_set_pb(
+ idx_pb, idx_dn,
+ LDAP_SCOPE_BASE,
+ idx_filter, NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(idx_pb);
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
+
+ if (idx_entries && idx_entries[0]) {
+ /* Index exists, check if it has integerOrderingMatch */
+ Slapi_Attr *mr_attr = NULL;
+ if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
+ Slapi_Value *sval = NULL;
+ int idx;
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
+ idx != -1;
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
+ const struct berval *bval = slapi_value_get_berval(sval);
+ if (bval && bval->bv_val &&
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
+ has_matching_rule = PR_TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!has_matching_rule) {
+ /* Index exists but doesn't have integerOrderingMatch, log a warning */
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
+ "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
+ "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
+ "To fix this, add the matching rule and reindex: "
+ "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
+ "dsconf <instance> backend index reindex --attr %s %s. "
+ "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
+ "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
+ attr_name, be_name, attr_name, be_name, attr_name, be_name);
+ }
+ }
+
+ slapi_ch_free_string(&idx_dn);
+ slapi_free_search_results_internal(idx_pb);
+ slapi_pblock_destroy(idx_pb);
+ }
+ }
+ }
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return uresult;
+}
+
upgrade_status
upgrade_server(void)
{
@@ -306,6 +407,10 @@ upgrade_server(void)
return UPGRADE_FAILURE;
}
+ if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
+ return UPGRADE_FAILURE;
+ }
+
return UPGRADE_SUCCESS;
}
--
2.52.0

View File

@ -0,0 +1,54 @@
From 56dde3de7c90d9d81f4588a5afda2cfa05bdb20f Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 24 Apr 2026 11:19:04 +0200
Subject: [PATCH 4/8] Issue 7437 - LeakSanitizer: memory leaks in CoS cache
error paths (#7438)
Description:
Fix memory leaks in CoS plugin when a CoS definition fails validation
or is incomplete.
Fixes: https://github.com/389ds/389-ds-base/issues/7437
Reviewed by: @mreynolds389 (Thanks!)
---
ldap/servers/plugins/cos/cos_cache.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/ldap/servers/plugins/cos/cos_cache.c b/ldap/servers/plugins/cos/cos_cache.c
index d8ebbbe3d..7074cb60a 100644
--- a/ldap/servers/plugins/cos/cos_cache.c
+++ b/ldap/servers/plugins/cos/cos_cache.c
@@ -1007,6 +1007,14 @@ cos_dn_defs_cb(Slapi_Entry *e, void *callback_data)
cos_cache_del_attrval_list(&pCosSpecifier);
if (pCosAttribute)
cos_cache_del_attrval_list(&pCosAttribute);
+ if (pCosOverrides)
+ cos_cache_del_attrval_list(&pCosOverrides);
+ if (pCosOperational)
+ cos_cache_del_attrval_list(&pCosOperational);
+ if (pCosMerge)
+ cos_cache_del_attrval_list(&pCosMerge);
+ if (pCosOpDefault)
+ cos_cache_del_attrval_list(&pCosOpDefault);
if (pDn)
cos_cache_del_attrval_list(&pDn);
}
@@ -1430,6 +1438,14 @@ out:
cos_cache_del_attrval_list(spec);
if (pAttrs)
cos_cache_del_attrval_list(pAttrs);
+ if (pOverrides)
+ cos_cache_del_attrval_list(pOverrides);
+ if (pOperational)
+ cos_cache_del_attrval_list(pOperational);
+ if (pCosMerge)
+ cos_cache_del_attrval_list(pCosMerge);
+ if (pCosOpDefault)
+ cos_cache_del_attrval_list(pCosOpDefault);
}
slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "<-- cos_cache_add_defn\n");
--
2.54.0

View File

@ -1,67 +0,0 @@
From cae81cde000df9a28733c673ee7c9b8292d69ef3 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 12 Jan 2026 10:58:02 +0100
Subject: [PATCH] Issue 7172 - (2nd) Index ordering mismatch after upgrade
(#7180)
Commit 742c12e0247ab64e87da000a4de2f3e5c99044ab introduced a regression
where the check to skip creating parentid/ancestorid indexes if they
already exist was incorrect.
The `ainfo_get()` function falls back to returning
LDBM_PSEUDO_ATTR_DEFAULT attrinfo when the requested attribute is not
found.
Since LDBM_PSEUDO_ATTR_DEFAULT is created before the ancestorid check,
`ainfo_get()` returns LDBM_PSEUDO_ATTR_DEFAULT instead of NULL, causing
the ancestorid index creation to be skipped entirely.
When operations later try to use the ancestorid index, they fall back to
LDBM_PSEUDO_ATTR_DEFAULT, and attempting to open the .default dbi
mid-transaction fails with MDB_NOTFOUND (-30798).
Fix Description:
Instead of just checking if `ainfo_get()` returns non-NULL, verify that
the returned attrinfo is actually for the requested attribute.
Fixes: https://github.com/389ds/389-ds-base/issues/7172
Reviewed by: @tbordaz (Thanks!)
---
ldap/servers/slapd/back-ldbm/instance.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
index 30db99c81..23bd70243 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -191,7 +191,7 @@ ldbm_instance_create_default_indexes(backend *be)
char *ancestorid_indexes_limit = NULL;
char *parentid_indexes_limit = NULL;
struct attrinfo *ai = NULL;
- struct attrinfo *index_already_configured = NULL;
+ int index_already_configured = 0;
struct index_idlistsizeinfo *iter;
int cookie;
int limit;
@@ -257,7 +257,8 @@ ldbm_instance_create_default_indexes(backend *be)
}
ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
- index_already_configured = ai;
+ /* Check if the attrinfo is actually for parentid, not a fallback to .default */
+ index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0);
if (!index_already_configured) {
e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
ldbm_instance_config_add_index_entry(inst, e, flags);
@@ -302,7 +303,8 @@ ldbm_instance_create_default_indexes(backend *be)
* but we still want to use the attr index file APIs.
*/
ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
- index_already_configured = ai;
+ /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
+ index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
if (!index_already_configured) {
e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
ldbm_instance_config_add_index_entry(inst, e, flags);
--
2.52.0

View File

@ -0,0 +1,169 @@
From f34eff84824f64470a8c181a0f7a8492695eaab3 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 5 May 2026 11:13:47 +0200
Subject: [PATCH 5/8] Issue 7372 - Reindex adds tombstones to ancestorid
causing export failures (#7373)
Bug Description:
During import/reindex, tombstone entries are added to the ancestorid
index. When those tombstones are later purged, the purge thread skips
updates to ancestorid index. This leaves stale entry IDs in the
ancestorid index referencing entries in id2entry that are no longer
there.
Fix Description:
Skip tombstone entries when building the ancestorid index during
import/reindex.
Fixes: https://github.com/389ds/389-ds-base/issues/7372
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
---
.../export/export_reindex_tombstone_test.py | 107 ++++++++++++++++++
.../slapd/back-ldbm/db-bdb/bdb_ldif2db.c | 2 +-
.../back-ldbm/db-mdb/mdb_import_threads.c | 2 +-
3 files changed, 109 insertions(+), 2 deletions(-)
create mode 100644 dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py
diff --git a/dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py b/dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py
new file mode 100644
index 000000000..3cb6e6178
--- /dev/null
+++ b/dirsrvtests/tests/suites/export/export_reindex_tombstone_test.py
@@ -0,0 +1,107 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2026 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+import os
+import time
+import pytest
+import ldap
+from lib389.idm.user import UserAccounts
+from lib389.idm.domain import Domain
+from lib389.tombstone import Tombstones
+from lib389.topologies import topology_m2 as topo_m2
+from lib389._constants import DEFAULT_SUFFIX, DEFAULT_BENAME, ErrorLog
+from lib389.utils import *
+from lib389.backend import Backends
+from lib389.replica import Replicas, ReplicationManager
+
+pytestmark = pytest.mark.tier1
+
+
+def test_export_after_reindex_and_tombstone_purge(topo_m2):
+ """Test that export with -s works after reindex and tombstone purge.
+
+ :id: 8c5cb603-1f43-46ad-9935-5f518d6d7fe0
+ :setup: Two supplier replication topology
+ :steps:
+ 1. Add entries under ou=people on S1, wait for replication to S2
+ 2. Delete the entries on S1, wait for replication
+ 3. Perform additional modifications to advance the RUV
+ 4. Reindex S1
+ 5. Configure aggressive tombstone purging and wait for tombstones to be purged on S1
+ 6. Export ou=people,dc=example,dc=com with -s
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. Export completes successfully
+ """
+ S1 = topo_m2.ms["supplier1"]
+ S2 = topo_m2.ms["supplier2"]
+ PEOPLE = f"ou=people,{DEFAULT_SUFFIX}"
+
+ users = UserAccounts(S1, DEFAULT_SUFFIX, rdn="ou=people")
+ test_users = []
+ for i in range(5):
+ user = users.create_test_user(uid=1234 + i)
+ test_users.append(user)
+ log.info("Added 5 test entries under ou=people")
+
+ repl = ReplicationManager(DEFAULT_SUFFIX)
+ repl.wait_for_replication(S1, S2)
+
+ for user in test_users:
+ user.delete()
+ log.info("Deleted 5 test entries")
+
+ repl.wait_for_replication(S1, S2)
+
+ domain = Domain(S1, DEFAULT_SUFFIX)
+ for i in range(10):
+ domain.replace("description", f"advancing RUV {i}")
+ repl.wait_for_replication(S1, S2)
+
+ domain2 = Domain(S2, DEFAULT_SUFFIX)
+ for i in range(10):
+ domain2.replace("description", f"advancing RUV from S2 {i}")
+ repl.wait_for_replication(S2, S1)
+ log.info("RUV advanced on both suppliers")
+
+ S1.stop()
+ S1.db2index(DEFAULT_BENAME)
+ log.info("Reindex completed on S1")
+ S1.start()
+
+ replica = Replicas(S1).get(DEFAULT_SUFFIX)
+ replica.replace("nsDS5ReplicaPurgeDelay", "1")
+ replica.replace("nsDS5ReplicaTombstonePurgeInterval", "1")
+
+ S1.config.loglevel((ErrorLog.REPLICA,), "error")
+
+ log.info("Waiting for tombstone purge on S1...")
+ tombstones = Tombstones(S1, PEOPLE)
+ for attempt in range(60):
+ time.sleep(2)
+ ts_list = tombstones.list()
+ if len(ts_list) == 0:
+ log.info(f"All tombstones purged after {(attempt + 1) * 2}s")
+ break
+ log.info(f"Attempt {attempt + 1}: {len(ts_list)} tombstones remaining")
+ else:
+ pytest.fail("Tombstones not purged after 120s")
+
+ S1.deleteErrorLogs()
+
+ backends = Backends(S1)
+ task = backends.export_ldif(be_names=[DEFAULT_BENAME], include_suffixes=[PEOPLE])
+ task.wait()
+ assert task.is_complete()
+ assert task.get_exit_code() == 0
+
+ log.info("Export after reindex + tombstone purge succeeded")
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
index f5127cc66..5138646f6 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
@@ -2053,7 +2053,7 @@ bdb_db2index(Slapi_PBlock *pb)
/*
* Update the ancestorid and entryrdn index
*/
- if (!entryrdn_get_noancestorid() && (index_ext & DB2INDEX_ANCESTORID)) {
+ if (!entryrdn_get_noancestorid() && (index_ext & DB2INDEX_ANCESTORID) && !istombstone) {
rc = ldbm_ancestorid_index_entry(be, ep, BE_INDEX_ADD, NULL);
if (rc != 0) {
slapi_log_err(SLAPI_LOG_ERR,
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
index d37f223fd..35965df85 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
@@ -3087,7 +3087,7 @@ process_entryrdn(backentry *ep, WorkerQueueData_t *wqelmnt)
add_update_entry_operational_attributes(ep, 0);
}
- if (ctx->ancestorid && wqelmnt->entry_info) {
+ if (ctx->ancestorid && wqelmnt->entry_info && wqelmnt->dnrc != DNRC_TOMBSTONE) {
/* Update ancestorids */
wqd.dbi = ctx->ancestorid->dbi;
for (n=0; n<wqelmnt->entry_info[INFO_IDX_NB_ANCESTORS]; n++) {
--
2.54.0

View File

@ -1,235 +0,0 @@
From c5aab4f8ba822572daa9ef69a0109577bf2147e1 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 20 Jan 2026 09:52:47 +0100
Subject: [PATCH] Issue 7189 - DSBLE0007 generates incorrect remediation
commands for scan limits
Bug Description:
The generated dsconf commands for fixing missing system indexes had two issues:
1. The --add-scanlimit value was not quoted, causing the shell to interpret
"limit=5000 type=eq flags=AND" as multiple arguments instead of a single
value, resulting in "unrecognized arguments: type=eq flags=AND" error.
2. When both matching rule and scanlimit were missing, two separate commands
were generated where the second would fail because the matching rule was
already added by the first command.
Fix Description:
1. Quote the scanlimit value in all remediation commands
2. Combine matching rule and scanlimit fixes into a single command when
both are missing for the same index instead of expected_scanlimit)
Fixes: https://github.com/389ds/389-ds-base/issues/7189
Reviewed by: @progier389, @droideck (Thanks!)
---
.../healthcheck/health_system_indexes_test.py | 126 ++++++++++++++++++
src/lib389/lib389/backend.py | 39 +++---
2 files changed, 147 insertions(+), 18 deletions(-)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index 6293340ca..5eadf6283 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -405,6 +405,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
+def test_missing_scanlimit(topology_st, log_buffering_enabled):
+ """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
+
+ :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
+ :setup: Standalone instance
+ :steps:
+ 1. Create DS instance
+ 2. Remove nsIndexIDListScanLimit from parentId index
+ 3. Use healthcheck without --json option
+ 4. Use healthcheck with --json option
+ 5. Verify the remediation command has properly quoted scanlimit
+ 6. Re-add the scanlimit
+ 7. Use healthcheck without --json option
+ 8. Use healthcheck with --json option
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. healthcheck reports DSBLE0007 code and related details
+ 4. healthcheck reports DSBLE0007 code and related details
+ 5. The scanlimit value is quoted in the remediation command
+ 6. Success
+ 7. healthcheck reports no issues found
+ 8. healthcheck reports no issues found
+ """
+
+ RET_CODE = "DSBLE0007"
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
+
+ standalone = topology_st.standalone
+
+ log.info("Remove nsIndexIDListScanLimit from parentId index")
+ parentid_index = Index(standalone, PARENTID_DN)
+ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
+
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
+
+ # Verify the remediation command has properly quoted scanlimit
+ args = FakeArgs()
+ args.instance = standalone.serverid
+ args.verbose = standalone.verbose
+ args.list_errors = False
+ args.list_checks = False
+ args.exclude_check = []
+ args.check = ["backends"]
+ args.dry_run = False
+ args.json = False
+ health_check_run(standalone, topology_st.logcap.log, args)
+ # Check that the scanlimit is quoted in the output
+ assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
+ log.info("Verified scanlimit is properly quoted in remediation command")
+ topology_st.logcap.flush()
+
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
+
+ log.info("Re-add the nsIndexIDListScanLimit")
+ parentid_index = Index(standalone, PARENTID_DN)
+ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
+
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
+
+
+def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
+ """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
+
+ :id: af8214ad-5e4c-422a-8f74-3e99227551df
+ :setup: Standalone instance
+ :steps:
+ 1. Create DS instance
+ 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
+ 3. Use healthcheck and verify a single combined command is generated
+ 4. Re-add the matching rule and scanlimit
+ 5. Use healthcheck without --json option
+ 6. Use healthcheck with --json option
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
+ 4. Success
+ 5. healthcheck reports no issues found
+ 6. healthcheck reports no issues found
+ """
+
+ RET_CODE = "DSBLE0007"
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
+
+ standalone = topology_st.standalone
+
+ log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
+ parentid_index = Index(standalone, PARENTID_DN)
+ parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
+ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
+
+ # Run healthcheck and verify combined command
+ args = FakeArgs()
+ args.instance = standalone.serverid
+ args.verbose = standalone.verbose
+ args.list_errors = False
+ args.list_checks = False
+ args.exclude_check = []
+ args.check = ["backends"]
+ args.dry_run = False
+ args.json = False
+ health_check_run(standalone, topology_st.logcap.log, args)
+
+ # Verify DSBLE0007 is reported
+ assert topology_st.logcap.contains(RET_CODE)
+ log.info("healthcheck returned code: %s" % RET_CODE)
+
+ # Verify a single combined command is generated with both --add-mr and --add-scanlimit
+ assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
+ log.info("Verified combined command with both --add-mr and --add-scanlimit")
+
+ topology_st.logcap.flush()
+
+ log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
+ parentid_index = Index(standalone, PARENTID_DN)
+ parentid_index.add("nsMatchingRule", "integerOrderingMatch")
+ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
+
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
+
+
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index 024de2adb..d3c9ccf35 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -678,7 +678,7 @@ class Backend(DSLdapObject):
if expected_config.get('matching_rule'):
cmd += f" --matching-rule {expected_config['matching_rule']}"
if expected_config.get('scanlimit'):
- cmd += f" --add-scanlimit {expected_config['scanlimit']}"
+ cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
remediation_commands.append(cmd)
reindex_attrs.add(attr_name) # New index needs reindexing
else:
@@ -700,28 +700,31 @@ class Backend(DSLdapObject):
remediation_commands.append(cmd)
reindex_attrs.add(attr_name)
- # Check matching rules
+ # Check matching rules and scanlimit together to generate a single combined command
expected_mr = expected_config.get('matching_rule')
+ expected_scanlimit = expected_config.get('scanlimit')
+
+ missing_mr = False
if expected_mr:
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
if expected_mr.lower() not in actual_mrs_lower:
discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
- # Add the missing matching rule
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
- remediation_commands.append(cmd)
- reindex_attrs.add(attr_name)
-
- # Check fine grain definitions for parentid ONLY
- expected_scanlimit = expected_config.get('scanlimit')
- if (attr_name.lower() == "parentid") and expected_scanlimit and (len(actual_scanlimit) == 0):
- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_mr}")
- # Add the missing scanlimit
- if expected_mr:
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr} --add-scanlimit {expected_scanlimit}"
- else:
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-scanlimit {expected_scanlimit}"
- remediation_commands.append(cmd)
- reindex_attrs.add(attr_name)
+ missing_mr = True
+
+ missing_scanlimit = False
+ if expected_scanlimit and (len(actual_scanlimit) == 0):
+ discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
+ missing_scanlimit = True
+
+ # Generate a single combined command for all missing items
+ if missing_mr or missing_scanlimit:
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
+ if missing_mr:
+ cmd += f" --add-mr {expected_mr}"
+ if missing_scanlimit:
+ cmd += f" --add-scanlimit \"{expected_scanlimit}\""
+ remediation_commands.append(cmd)
+ reindex_attrs.add(attr_name)
except Exception as e:
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
--
2.52.0

View File

@ -0,0 +1,199 @@
From af0216be875e3580f5c916c7ca9ab63db4df32f7 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Wed, 8 Apr 2026 23:56:15 +0100
Subject: [PATCH 6/8] Issue 7327 - dsctl healthcheck DSMOLE0001 inaccurate
recommendations with multiple backends (#7328)
Description:
The dsctl healthcheck tool generates incorrect recommendations for the MO plugin when
multiple backends are present. This commonly occurs in IPA environments where both
userroot and ipaca backends exist. Healthcheck incorrectly suggests indexing attributes
for backends that are not within the MemberOf plugin scope.
Fix:
Determine the MO plugin scope while iterating over backends and only generate
recommendations for backends that fall within that scope.
Removed references to nsslapd-plugincontainerscope as we dont use it.
Fixes:
https://github.com/389ds/389-ds-base/issues/7327
Reviewed by: @droideck (Thank you)
---
.../memberof_include_scopes_test.py | 86 +++++++++++++++++++
src/lib389/lib389/plugins.py | 30 ++++---
2 files changed, 106 insertions(+), 10 deletions(-)
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
index 347eb880f..8cdf94f3c 100644
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
@@ -10,6 +10,7 @@ import pytest
import os
import ldap
import time
+from lib389.backend import Backends
from lib389.utils import ensure_str
from lib389.topologies import topology_st as topo
from lib389._constants import *
@@ -17,6 +18,8 @@ from lib389.plugins import MemberOfPlugin, ReferentialIntegrityPlugin
from lib389.idm.user import UserAccount, UserAccounts
from lib389.idm.group import Group, Groups
from lib389.idm.nscontainer import nsContainers
+from lib389.idm.domain import Domain
+from lib389.idm.organizationalunit import OrganizationalUnit
SUBTREE_1 = 'cn=sub1,%s' % SUFFIX
SUBTREE_2 = 'cn=sub2,%s' % SUFFIX
@@ -131,6 +134,89 @@ def test_multiple_scopes(topo):
assert not group.present("member", INCLUDED_USER)
assert not group.present("member", EXCLUDED_USER)
+def test_memberof_scope_multiple_backends(topology_st):
+ """Test memberOf plugin correctly handles multiple backends with different scopes
+
+ :id: 96419128-70a4-4a81-943e-8d1e9c92f241
+ :setup: Instance with multiple backends
+ :steps:
+ 1. Create out of scope backend
+ 2. Create domain and OU's for out of scope backend
+ 3. Create groups and users in both backends
+ 4. Enable MO plugin and set scope to backend 1
+ 5. Add users to groups in both backends
+ 6. Verify in scope user has memberOf attr value of in scope group dn
+ 7. Verify out scope user has an empty memberOf attr value
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success, trigger MO update
+ 6. Success
+ 7. Success
+ """
+ inst = topology_st.standalone
+ outscope_suffix = 'dc=other,dc=org'
+
+ # Create a backend 2
+ backends = Backends(inst)
+ outscope_be = backends.create(properties={
+ 'cn': 'testBackend',
+ 'nsslapd-suffix': outscope_suffix
+ })
+
+ # Create domain entry for backend 2
+ domain = Domain(inst, outscope_suffix)
+ domain.create(properties={
+ 'dc': 'other'
+ })
+
+ # Create OUs for backend 2
+ people_ou = OrganizationalUnit(inst, f'ou=People,{outscope_suffix}')
+ people_ou.create(properties={'ou': 'People'})
+ groups_ou = OrganizationalUnit(inst, f'ou=Groups,{outscope_suffix}')
+ groups_ou.create(properties={'ou': 'Groups'})
+
+ # Create test groups and users
+ inscope_users = UserAccounts(inst, 'dc=example,dc=com')
+ inscope_groups = Groups(inst, 'dc=example,dc=com')
+ inscope_user = inscope_users.create_test_user(uid=1000)
+ inscope_group = inscope_groups.create(properties={'cn': 'testgroup1'}) # NO MEMBERS initially
+
+ outscope_users = UserAccounts(inst, outscope_suffix)
+ outscope_groups = Groups(inst, outscope_suffix)
+ outscope_user = outscope_users.create_test_user(uid=2000)
+ outscope_group = outscope_groups.create(properties={'cn': 'testgroup2'}) # NO MEMBERS initially
+
+ # Configure memberof plugin with backend 1 scope
+ memberof = MemberOfPlugin(inst)
+ memberof.enable()
+ memberof.replace('memberOfEntryScope', 'dc=example,dc=com')
+ inst.restart()
+
+ # Trigger memberof processing
+ inscope_group.add('member', inscope_user.dn)
+ outscope_group.add('member', outscope_user.dn)
+
+ # Sleep for a bit
+ time.sleep(2)
+
+ # In scope user should have memberOf attribute
+ inscope_user_entry = inscope_user.get_attrs_vals_utf8(['memberOf'])
+ assert inscope_user_entry['memberOf'] == [inscope_group.dn]
+
+ # Out of scope user should not have memberOf attribute
+ outscope_user_entry = outscope_user.get_attrs_vals_utf8(['memberOf'])
+ assert outscope_user_entry['memberOf'] == []
+
+ # Cleanup
+ inscope_group.delete()
+ inscope_user.delete()
+ outscope_group.delete()
+ outscope_user.delete()
+ outscope_be.delete()
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index 483d86659..0c0148119 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -794,16 +794,21 @@ class MemberOfPlugin(Plugin):
from lib389.backend import Backends
backends = Backends(self._instance).list()
attrs = self.get_attr_vals_utf8_l("memberofgroupattr")
- container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
+ scopes = self.get_attr_vals_utf8_l("memberofentryscope")
for backend in backends:
suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
if suffix == "cn=changelog":
# Always skip retro changelog
continue
- if container is not None:
- # Check if this backend is in the scope
- if not container.endswith(suffix):
- # skip this backend that is not in the scope
+ if scopes:
+ # Is this backend suffix in scope
+ in_scope = False
+ for scope in scopes:
+ if scope.endswith(suffix) or suffix.endswith(scope):
+ in_scope = True
+ break
+
+ if not in_scope:
continue
indexes = backend.get_indexes()
for attr in attrs:
@@ -842,16 +847,21 @@ class MemberOfPlugin(Plugin):
from lib389.backend import Backends
backends = Backends(self._instance).list()
membership_attrs = ['member', 'uniquemember']
- container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
+ scopes = self.get_attr_vals_utf8_l("memberofentryscope")
for backend in backends:
suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
if suffix == "cn=changelog":
# Always skip retro changelog
continue
- if container is not None:
- # Check if this backend is in the scope
- if not container.endswith(suffix):
- # skip this backend that is not in the scope
+ if scopes:
+ # Is this backend suffix in scope
+ in_scope = False
+ for scope in scopes:
+ if scope.endswith(suffix) or suffix.endswith(scope):
+ in_scope = True
+ break
+
+ if not in_scope:
continue
indexes = backend.get_indexes()
for attr in membership_attrs:
--
2.54.0

View File

@ -1,48 +0,0 @@
From 327ebf93decaa19bd3919afaeba0dc122eef1990 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 12 Jan 2026 13:53:05 -0500
Subject: [PATCH] Issue 7184 - argparse.HelpFormatter _format_actions_usage()
is deprecated
Description:
_format_actions_usage() was removed in python 3.15. Instead we can use
_get_actions_usage_parts() but it also behaves differently between
python 3.14 and 3.15 so we need special handling.
Relates: https://github.com/389ds/389-ds-base/issues/7184
Reviewed by: spichugi(Thanks!)
---
src/lib389/lib389/cli_base/__init__.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
index 06b8f9964..f1055aadc 100644
--- a/src/lib389/lib389/cli_base/__init__.py
+++ b/src/lib389/lib389/cli_base/__init__.py
@@ -413,7 +413,20 @@ class CustomHelpFormatter(argparse.HelpFormatter):
def _format_usage(self, usage, actions, groups, prefix):
usage = super(CustomHelpFormatter, self)._format_usage(usage, actions, groups, prefix)
- formatted_options = self._format_actions_usage(parent_arguments, [])
+
+ if sys.version_info < (3, 13):
+ # Use _format_actions_usage() for Python 3.12 and earlier
+ formatted_options = self._format_actions_usage(parent_arguments, [])
+ else:
+ # Use _get_actions_usage_parts() for Python 3.13 and later
+ action_parts = self._get_actions_usage_parts(parent_arguments, [])
+ if sys.version_info >= (3, 15):
+ # Python 3.15 returns a tuple (list of actions, count of actions)
+ formatted_options = ' '.join(action_parts[0])
+ else:
+ # Python 3.13 and 3.14 return a list of actions
+ formatted_options = ' '.join(action_parts)
+
# If formatted_options already in usage - remove them
if formatted_options in usage:
usage = usage.replace(f' {formatted_options}', '')
--
2.52.0

View File

@ -0,0 +1,651 @@
From 50ee439bb1d2e2344ea8cef076d54585204d9fbb Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Wed, 25 Feb 2026 18:00:24 +0100
Subject: [PATCH 7/8] Issue 7267 - MDB_BAD_VALSIZE error when updating index
(#7268)
* Issue 7267 - MDB_BAD_VALSIZE error when updating index
* Improve import log when writer fails
* Fix Sourcery AI comments
* Fix INDEX_KEY_LENGTH typo
Problem with the key prefix handling when key is too long and must be hashed.
The issue is that the # that is prepended is not reset when iterating over the valueset values (Ending up with very long prefix)
Also refactored the code to avoid duplicate the code that prepare the key from the attribute value (used when updating the index or retrieving a value from an index)
Issue: #7267
Reviewed by: @tbordaz , @vashirov (Thanks!)
Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
---------
Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
---
.../tests/suites/indexes/regression_test.py | 58 +++++++
ldap/servers/slapd/back-ldbm/attrcrypt.h | 2 +-
ldap/servers/slapd/back-ldbm/back-ldbm.h | 2 +
.../slapd/back-ldbm/db-bdb/bdb_import.c | 39 +----
.../back-ldbm/db-mdb/mdb_import_threads.c | 46 ++++-
ldap/servers/slapd/back-ldbm/index.c | 161 +++++++-----------
ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c | 13 +-
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 2 +-
ldap/servers/slapd/log.c | 40 +++++
ldap/servers/slapd/slapi-private.h | 2 +
10 files changed, 221 insertions(+), 144 deletions(-)
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
index e2c2f5c35..53d340cdd 100644
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
@@ -699,6 +699,64 @@ def test_idl_range_limit(topo, add_some_entries):
assert len(entries) == 3
+def test_large_multivalued_sn_attribute(topo):
+ """Test adding a user entry with 512 values for sn attribute, each 512 bytes
+
+ :id: 8f2a9b3c-e8d7-11ef-9a5f-482ae39447e5
+ :setup: Standalone Instance
+ :steps:
+ 1. Create a user with 512 sn values, each 512 bytes long
+ 2. Verify the user was created successfully
+ 3. Search for the user and verify all sn values are present
+ 4. Clean up the user entry
+ :expectedresults:
+ 1. User is created successfully
+ 2. User entry exists
+ 3. All 512 sn values are present and have correct length
+ 4. User is deleted successfully
+ """
+
+ inst = topo.standalone
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+
+ log.info("Creating user with 512 sn values, each 512 bytes")
+
+ # Generate 512 unique sn values, each 512 bytes long
+ # Use a pattern that makes each value unique but predictable
+ sn_values = []
+ for i in range(512):
+ # Create a 512-byte value with unique identifier at the start
+ value = f'sn_value_{i:04d}_' + 'x' * (512 - len(f'sn_value_{i:04d}_'))
+ sn_values.append(value)
+
+ # Create the user with first sn value
+ user_name = 'test_user_large_sn'
+ user = users.create(properties={
+ 'uid': user_name,
+ 'cn': user_name,
+ 'sn': sn_values,
+ 'uidNumber': '99999',
+ 'gidNumber': '99999',
+ 'homeDirectory': f'/home/{user_name}'
+ })
+
+ # Verify the entry was created and has all sn values
+ log.info("Verifying all sn values are present")
+ sn_attr_values = user.get_attr_vals_utf8('sn')
+
+ assert len(sn_attr_values) == 512, f"Expected 512 sn values, got {len(sn_attr_values)}"
+
+ # Verify each value has the correct length
+ for idx, value in enumerate(sn_attr_values):
+ assert len(value) == 512, f"sn value {idx} has length {len(value)}, expected 512"
+
+ log.info("Successfully created and verified user with 512 sn values of 512 bytes each")
+
+ # Clean up
+ user.delete()
+ log.info("User entry deleted successfully")
+
+
if __name__ == "__main__":
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/attrcrypt.h b/ldap/servers/slapd/back-ldbm/attrcrypt.h
index d653ba951..dcbea80fe 100644
--- a/ldap/servers/slapd/back-ldbm/attrcrypt.h
+++ b/ldap/servers/slapd/back-ldbm/attrcrypt.h
@@ -10,7 +10,7 @@
#include <config.h>
#endif
-/* Private tructures and #defines used in the attribute encryption code. */
+/* Private structures and #defines used in the attribute encryption code. */
#ifndef _ATTRCRYPT_H_
#define _ATTRCRYPT_H_
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
index 70f532538..f77924f1a 100644
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
@@ -104,6 +104,8 @@ typedef unsigned short u_int16_t;
*/
#define BE_CHANGELOG_FILE "replication_changelog"
+#define INDEX_KEY_LENGTH(lenval,lenprefix) (lenval+lenprefix+2)
+
#define BDB_IMPL "bdb"
#define BDB_BACKEND "libback-ldbm" /* This backend plugin */
#define BDB_NEWIDL "newidl" /* new idl format */
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
index 59a472f58..bdc4ee4f6 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
@@ -75,9 +75,9 @@ static IDList *bdb_idl_union_allids(backend *be, struct attrinfo *ai, IDList *a,
#define DEBUG_SUBCOUNT_MSG(msg, ...) { debug_subcount(__FUNCTION__, __LINE__, (msg), __VA_ARGS__); }
#define DUMP_SUBCOUNT_KEY(msg, key, ret) { debug_subcount(__FUNCTION__, __LINE__, "ret=%d size=%u ulen=%u doff=%u dlen=%u", \
ret, (key).size, (key).ulen, (key).doff, (key).dlen); \
- if (ret == 0) hexadump(msg, (key).data, 0, (key).size); \
+ if (ret == 0) slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).size); \
else if (ret == DB_BUFFER_SMALL) \
- hexadump(msg, (key).data, 0, (key).ulen); }
+ slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).ulen); }
static void
debug_subcount(const char *funcname, int line, char *msg, ...)
@@ -90,41 +90,6 @@ debug_subcount(const char *funcname, int line, char *msg, ...)
slapi_log_err(SLAPI_LOG_INFO, (char*)funcname, "DEBUG SUBCOUNT [%d] %s\n", line, buff);
}
-/*
- * Dump a memory buffer in hexa and ascii in error log
- *
- * addr - The memory buffer address.
- * len - The memory buffer lenght.
- */
-static void
-hexadump(char *msg, const void *addr, size_t offset, size_t len)
-{
-#define HEXADUMP_TAB 4
-/* 4 characters per bytes: 2 hexa digits, 1 space and the ascii */
-#define HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
- char hexdigit[] = "0123456789ABCDEF";
-
- const unsigned char *pt = addr;
- char buff[HEXADUMP_BUF_SIZE+1];
- memset (buff, ' ', HEXADUMP_BUF_SIZE);
- buff[HEXADUMP_BUF_SIZE] = '\0';
- while (len > 0) {
- int dpl;
- for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
- buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
- buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
- buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
- pt++;
- }
- for (;dpl < 16; dpl++) {
- buff[3*dpl] = ' ';
- buff[3*dpl+1] = ' ';
- buff[3*16+HEXADUMP_TAB+dpl] = ' ';
- }
- slapi_log_err(SLAPI_LOG_INFO, msg, "[0x%08lx] %s\n", offset, buff);
- offset += 16;
- }
-}
#else
#define DEBUG_SUBCOUNT_MSG(msg, ...)
#define DUMP_SUBCOUNT_KEY(msg, key, ret)
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
index 35965df85..c49412a70 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
@@ -1122,6 +1122,21 @@ dbmdb_import_entry_info_by_backentry(mdb_privdb_t *db, BulkQueueData_t *bqdata,
return dnrc;
}
+/* Log wqelmt details */
+void
+log_wqelmt(int loglvl, char *fname, WorkerQueueData_t *wqelmt)
+{
+ if (wqelmt->dn) {
+ slapi_log_err(loglvl, fname, "log_wqelmt: dn=%s\n", wqelmt->dn);
+ }
+ if (wqelmt->filename && wqelmt->lineno) {
+ slapi_log_err(loglvl, fname, "log_wqelmt: ldif=%s[%d]\n", wqelmt->filename, wqelmt->lineno);
+ }
+ if (wqelmt->data) {
+ size_t len = wqelmt->datalen ? wqelmt->datalen : strlen(wqelmt->data);
+ slapi_log_hexadump(loglvl, "log_wqelmt:data", wqelmt->data, len);
+ }
+}
/* producer thread for ldif import case:
* read through the given file list, parsing entries (str2entry), assigning
@@ -1255,6 +1270,7 @@ dbmdb_import_producer(void *param)
import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_import_producer",
"ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
wqelmt.dnrc);
+ log_wqelmt(SLAPI_LOG_ERR, "dbmdb_import_producer", &wqelmt);
abort();
case DNRC_OK:
case DNRC_SUFFIX:
@@ -1761,6 +1777,7 @@ dbmdb_index_producer(void *param)
import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_index_producer",
"ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
tmpslot.dnrc);
+ log_wqelmt(SLAPI_LOG_ERR, "dbmdb_index_producer", &tmpslot);
abort();
case DNRC_OK:
case DNRC_SUFFIX:
@@ -3951,10 +3968,24 @@ dbmdb_import_writer(void*param)
if (!txn) {
MDB_STAT_STEP(stats, MDB_STAT_TXNSTART);
rc = TXN_BEGIN(ctx->ctx->env, NULL, 0, &txn);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to begin a txn. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
+ }
}
if (!rc) {
MDB_STAT_STEP(stats, MDB_STAT_WRITE);
rc = MDB_PUT(txn, slot->dbi->dbi, &slot->key, &slot->data, 0);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to write record in dbi %s. Error is 0x%x: %s.\n",
+ slot->dbi->dbname, rc, mdb_strerror(rc));
+ slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:key",
+ slot->key.mv_data, slot->key.mv_size);
+ slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:data",
+ slot->data.mv_data, slot->data.mv_size);
+ }
}
MDB_STAT_STEP(stats, MDB_STAT_RUN);
nextslot = slot->next;
@@ -3968,6 +3999,9 @@ dbmdb_import_writer(void*param)
rc = TXN_COMMIT(txn);
MDB_STAT_STEP(stats, MDB_STAT_RUN);
if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to commit the txn. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
break;
}
count = 0;
@@ -3980,6 +4014,10 @@ dbmdb_import_writer(void*param)
MDB_STAT_STEP(stats, MDB_STAT_RUN);
if (!rc) {
txn = NULL;
+ } else {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to commit the txn. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
}
}
if (txn) {
@@ -3992,13 +4030,17 @@ dbmdb_import_writer(void*param)
if (!rc) {
/* Ensure that all data are written on disk */
rc = mdb_env_sync(ctx->ctx->env, 1);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "mdb_env_sync failed. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
+ }
}
MDB_STAT_END(stats);
if (rc) {
slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
- "Failed to write in the database. Error is 0x%x: %s.\n",
- rc, mdb_strerror(rc));
+ "Aborting import after failure.\n");
thread_abort(info);
} else {
char buf[200];
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
index ca3a866bb..3bdf8926b 100644
--- a/ldap/servers/slapd/back-ldbm/index.c
+++ b/ldap/servers/slapd/back-ldbm/index.c
@@ -891,6 +891,67 @@ index_read(
return index_read_ext(be, (char *)type, indextype, val, txn, err, NULL);
}
+/* Prepare an index key (hashed if too long, encrypted if needed from attribute value */
+int
+prepare_key(backend *be, struct attrinfo *a, char **buf, size_t *buflen,
+ int flags, const char *prefix, const struct berval *bvp, dbi_val_t *key)
+{
+ /* Key format is [Hash?] [prefix] [val] [\0] */
+ struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
+ size_t plen = strlen(prefix);
+ struct berval *hashed_bvp = NULL;
+ struct berval *encrypted_bvp = NULL;
+ int rc = 0;
+
+ /* Hash large index key if necessary */
+ if (INDEX_KEY_LENGTH(bvp->bv_len,plen) >= li->li_max_key_len) {
+ rc = attrcrypt_hash_large_index_key(be, prefix, a, bvp, &hashed_bvp);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
+ "Failed to hash large index key for %s\n", a->ai_type);
+ return rc;
+ } else {
+ bvp = hashed_bvp;
+ }
+ }
+
+ /* Encrypt the index key if necessary */
+ if (rc == 0 && a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
+ rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
+ "Failed to encrypt index key for %s\n", a->ai_type);
+ } else {
+ bvp = encrypted_bvp;
+ }
+ }
+ if (hashed_bvp) {
+ prefix = slapi_ch_smprintf("%c%s",HASH_PREFIX, prefix);
+ plen++;
+ }
+ if (buf && buflen) {
+ if (plen+bvp->bv_len+1 > *buflen) {
+ *buflen = plen+bvp->bv_len+1;
+ *buf = slapi_ch_realloc(*buf, *buflen);
+ }
+ dblayer_value_concat(be, key, *buf, *buflen, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
+ } else {
+ dblayer_value_concat(be, key, NULL, 0, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
+ }
+
+ if (hashed_bvp) {
+ ber_bvfree(hashed_bvp);
+ hashed_bvp = NULL;
+ slapi_ch_free_string((char**)&prefix);
+ }
+ if (encrypted_bvp) {
+ ber_bvfree(encrypted_bvp);
+ encrypted_bvp = NULL;
+ }
+ return rc;
+}
+
+
/*
* Extended version of index_read.
* The unindexed flag can be used to distinguish between a
@@ -927,7 +988,6 @@ index_read_ext_allids(
struct berval *hashed_val = NULL;
int is_and = 0;
unsigned int ai_flags = 0;
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
*err = 0;
@@ -1038,36 +1098,7 @@ index_read_ext_allids(
}
if (val != NULL) {
- size_t vlen;
- int ret = 0;
-
- /* If necessary, hash this index key */
- if (val->bv_len >= li->li_max_key_len) {
- ret = attrcrypt_hash_large_index_key(be, &prefix, ai, val, &hashed_val);
- if (ret) {
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
- "Failed to hash large index key for %s\n", basetype);
- *err = DBI_RC_OTHER;
- index_free_prefix(prefix);
- slapi_ch_free_string(&basetmp);
- return (NULL);
- }
- if (hashed_val) {
- val = hashed_val;
- }
- }
- /* If necessary, encrypt this index key */
- ret = attrcrypt_encrypt_index_key(be, ai, val, &encrypted_val);
- if (ret) {
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
- "Failed to encrypt index key for %s\n", basetype);
- }
- if (encrypted_val) {
- val = encrypted_val;
- }
- vlen = val->bv_len;
- dblayer_value_concat(be, &key, buf, sizeof(buf),
- prefix, strlen(prefix), val->bv_val, vlen, "", 1);
+ (void) prepare_key(be, ai, NULL, 0, 0, prefix, val, &key);
} else {
dblayer_value_concat(be, &key, buf, sizeof(buf), prefix, strlen(prefix),
"", 1, NULL, 0);
@@ -1867,6 +1898,7 @@ index_range_read(
return index_range_read_ext(pb, be, type, indextype, operator, val, nextval, range, txn, err, 0);
}
+
static int
addordel_values_sv(
backend *be,
@@ -1885,15 +1917,10 @@ addordel_values_sv(
int i = 0;
dbi_val_t key = {0};
dbi_txn_t *db_txn = NULL;
- size_t plen, vlen, len;
char *tmpbuf = NULL;
size_t tmpbuflen = 0;
- char *realbuf;
char *prefix = NULL;
const struct berval *bvp;
- struct berval *hashed_bvp = NULL;
- struct berval *encrypted_bvp = NULL;
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
char *index_id = get_index_name(be, db, a);
slapi_log_err(SLAPI_LOG_TRACE, "addordel_values_sv", "%s_values\n",
@@ -1932,66 +1959,14 @@ addordel_values_sv(
return (rc);
}
- plen = strlen(prefix);
for (i = 0; vals[i] != NULL; i++) {
bvp = slapi_value_get_berval(vals[i]);
- /* Hash large index key if necessary */
- if (bvp->bv_len >= li->li_max_key_len) {
- rc = attrcrypt_hash_large_index_key(be, &prefix, a, bvp, &hashed_bvp);
- if (rc) {
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
- "Failed to hash large index key for %s\n", a->ai_type);
- break;
- } else {
- bvp = hashed_bvp;
- plen = strlen(prefix);
- }
- }
- /* Encrypt the index key if necessary */
- {
- if (a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
- rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
- if (rc) {
- slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
- "Failed to encrypt index key for %s\n", a->ai_type);
- } else {
- bvp = encrypted_bvp;
- }
- }
+ rc = prepare_key(be, a, &tmpbuf, &tmpbuflen, flags, prefix, bvp, &key);
+ if (rc) {
+ break;
}
- vlen = bvp->bv_len;
- len = plen + vlen;
-
- if (len < tmpbuflen) {
- realbuf = tmpbuf;
- } else {
- tmpbuf = slapi_ch_realloc(tmpbuf, len + 1);
- tmpbuflen = len + 1;
- realbuf = tmpbuf;
- }
-
- assert(realbuf); /* For coverity */
- memcpy(realbuf, prefix, plen);
- memcpy(realbuf + plen, bvp->bv_val, vlen);
- realbuf[len] = '\0';
- /* Free the encrypted berval if necessary */
- if (hashed_bvp) {
- ber_bvfree(hashed_bvp);
- hashed_bvp = NULL;
- }
- if (encrypted_bvp) {
- ber_bvfree(encrypted_bvp);
- encrypted_bvp = NULL;
- }
- /* should be okay to use USERMEM here because we know what
- * the key is and it should never return a different value
- * than the one we pass in.
- */
- dblayer_value_set_buffer(be, &key, realbuf, plen + vlen + 1);
- key.ulen = tmpbuflen;
-
if (slapi_is_loglevel_set(LDAP_DEBUG_TRACE)) {
char encbuf[BUFSIZ];
@@ -2024,10 +1999,6 @@ addordel_values_sv(
ldbm_nasty(NASTY_MSG("addordel_values_sv"), index_id, 1130, rc);
break;
}
- if (NULL != key.dptr && realbuf != key.dptr) { /* realloc'ed */
- tmpbuf = key.dptr;
- tmpbuflen = key.size;
- }
}
index_free_prefix(prefix);
if (tmpbuf != NULL) {
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
index 3b86a5dd7..14b3b9ce2 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
@@ -1065,15 +1065,15 @@ attrcrypt_decrypt_index_key(backend *be,
* : NULL - no hash or failure
*/
int
-attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
+attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
{
int ret = 0;
struct berval *out_berval = NULL;
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
- char *new_prefix;
+ size_t final_key_len = INDEX_KEY_LENGTH(in->bv_len, strlen(prefix));
/* If the index key is too long (i.e mdb case) we must hash it */
- if (in->bv_len >= li->li_max_key_len) {
+ if (final_key_len >= li->li_max_key_len) {
PK11Context *c = PK11_CreateDigestContext(SEC_OID_MD5);
if (c != NULL) {
unsigned char hash[32];
@@ -1087,16 +1087,13 @@ attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai,
return ENOMEM;
}
slapi_log_err(SLAPI_LOG_TRACE, "attrcrypt_hash_large_index_key",
- "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", in->bv_len, li->li_max_key_len);
+ "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", final_key_len, li->li_max_key_len);
slapi_be_set_flag(be, SLAPI_BE_FLAG_DONT_BYPASS_FILTERTEST);
PK11_DigestBegin(c);
/* Compute hash for the key without the prefix */
PK11_DigestOp(c, (unsigned char *)in->bv_val, in->bv_len);
PK11_DigestFinal(c, hash, &hashLen, sizeof hash);
- /* Add HASH_PREFIX before the prefix */
- new_prefix = slapi_ch_smprintf("%c%s", HASH_PREFIX, *prefix);
- index_free_prefix(*prefix);
- *prefix = new_prefix;
+
/* Build the key: hash value in hexa */
hkey = slapi_ch_malloc(1+2*sizeof hash);
out_berval->bv_val = hkey;
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
index 8754a4847..1884da603 100644
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
@@ -620,7 +620,7 @@ int attrcrypt_encrypt_entry_inplace(backend *be, const struct backentry *inout);
int attrcrypt_encrypt_entry(backend *be, const struct backentry *in, struct backentry **out);
int attrcrypt_encrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
int attrcrypt_decrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
-int attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
+int attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
int attrcrypt_init(ldbm_instance *li);
int attrcrypt_cleanup_private(ldbm_instance *li);
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index ca8d481e5..17c7bbb7d 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -92,6 +92,10 @@ static int slapi_log_map[] = {
#define SLAPI_LOG_MAX SLAPI_LOG_DEBUG /* from slapi-plugin.h */
#define LOG_CHUNK 16384 /* zlib compression */
+#define HEXADUMP_TAB 4
+/* 4 characters per bytes: 2 hexa digits, 1 space and the ascii */
+#define HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
+
/**************************************************************************
* PROTOTYPES
*************************************************************************/
@@ -3065,6 +3069,42 @@ slapi_log_backtrace(int loglevel)
}
}
+/*
+ * Dump a memory buffer in hexa and ascii in error log
+ *
+ * addr - The memory buffer address.
+ * len - The memory buffer lenght.
+ */
+void
+slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len)
+{
+ char hexdigit[] = "0123456789ABCDEF";
+ const unsigned char *pt = addr;
+ char buff[HEXADUMP_BUF_SIZE+1];
+ size_t offset = 0;
+
+ if (!slapi_is_loglevel_set(loglevel)) {
+ return;
+ }
+ memset (buff, ' ', HEXADUMP_BUF_SIZE);
+ buff[HEXADUMP_BUF_SIZE] = '\0';
+ while (len > 0) {
+ int dpl;
+ for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
+ buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
+ buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
+ buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
+ pt++;
+ }
+ for (;dpl < 16; dpl++) {
+ buff[3*dpl] = ' ';
+ buff[3*dpl+1] = ' ';
+ buff[3*16+HEXADUMP_TAB+dpl] = ' ';
+ }
+ slapi_log_err(loglevel, fname, "[0x%08lx] %s\n", offset, buff);
+ offset += 16;
+ }
+}
/******************************************************************************
* write in the access log
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
index a55ea3ee1..97eed5a9b 100644
--- a/ldap/servers/slapd/slapi-private.h
+++ b/ldap/servers/slapd/slapi-private.h
@@ -1527,6 +1527,8 @@ void slapi_pblock_set_task_warning(Slapi_PBlock *pb, task_warning warning);
int slapi_exists_or_add_internal(Slapi_DN *dn, const char *filter, const char *entry, const char *modifier_name);
void slapi_log_backtrace(int loglevel);
+void slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len);
+
#ifdef __cplusplus
}
--
2.54.0

View File

@ -0,0 +1,39 @@
From 853d11cd6abc69a8e93172771b19b52950293e6f Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 5 Jun 2026 11:17:35 +0200
Subject: [PATCH 8/8] Fix test389 imports on older branches
---
dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py | 2 +-
dirsrvtests/tests/suites/features/ldap_controls_test.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py b/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py
index 77f839c40..adbe2861a 100644
--- a/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py
+++ b/dirsrvtests/tests/suites/basic/modrdn_bulk_children_test.py
@@ -13,7 +13,7 @@ import pytest
from lib389._constants import DEFAULT_SUFFIX
from lib389.idm.organizationalunit import OrganizationalUnit, OrganizationalUnits
from lib389.idm.user import UserAccount, UserAccounts
-from test389.topologies import topology_st as topo
+from lib389.topologies import topology_st as topo
pytestmark = pytest.mark.tier1
diff --git a/dirsrvtests/tests/suites/features/ldap_controls_test.py b/dirsrvtests/tests/suites/features/ldap_controls_test.py
index 59a58b21d..7d8b21c8f 100644
--- a/dirsrvtests/tests/suites/features/ldap_controls_test.py
+++ b/dirsrvtests/tests/suites/features/ldap_controls_test.py
@@ -12,7 +12,7 @@ import ldap
from ldap.controls import RequestControl
from ldap.controls.readentry import PostReadControl
from lib389.idm.user import UserAccounts, UserAccount
-from test389.topologies import topology_st
+from lib389.topologies import topology_st
from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD
pytestmark = pytest.mark.tier1
--
2.54.0

View File

@ -1,53 +0,0 @@
From dbabbe6a3c42b979d14960b355925c9748371cad Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 30 Jan 2026 12:00:13 +0100
Subject: [PATCH] Issue 7027 - (2nd) 389-ds-base OpenScanHub Leaks Detected
(#7211)
Fix Description:
Update coverity annotations.
Relates: https://github.com/389ds/389-ds-base/issues/7027
Reviewed by: @aadhikar (Thanks!)
---
ldap/servers/slapd/log.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 668a02454..6f57a3d9c 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -203,8 +203,8 @@ compress_log_file(char *log_name, int32_t mode)
if ((source = fopen(log_name, "r")) == NULL) {
/* Failed to open log file */
- /* coverity[leaked_storage] gzclose does close FD */
gzclose(outfile);
+ /* coverity[leaked_handle] gzclose does close FD */
return -1;
}
@@ -214,17 +214,17 @@ compress_log_file(char *log_name, int32_t mode)
if (bytes_written == 0)
{
fclose(source);
- /* coverity[leaked_storage] gzclose does close FD */
gzclose(outfile);
+ /* coverity[leaked_handle] gzclose does close FD */
return -1;
}
bytes_read = fread(buf, 1, LOG_CHUNK, source);
}
- /* coverity[leaked_storage] gzclose does close FD */
gzclose(outfile);
fclose(source);
PR_Delete(log_name); /* remove the old uncompressed log */
+ /* coverity[leaked_handle] gzclose does close FD */
return 0;
}
--
2.52.0

View File

@ -1,197 +0,0 @@
From 39d47ca91e866d30b35dca41d477ad4ee07f8a14 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Mon, 2 Feb 2026 15:39:18 +0100
Subject: [PATCH] Issue 7213 - MDB_BAD_VALSIZE error while handling VLV (#7214)
* Issue 7213 - MDB_BAD_VALSIZE error while handling VLV
Avoid failing lmdb operation when handling VLV index by truncating the key so that key+data is small enough.
Issue: #7213
Reviewed by: @mreynolds389 , @vashirov (Thanks!)
Assisted by: Claude A/I
(cherry picked from commit 5ebce22d4214bec5ed94ad84c4448164be99389a)
---
.../tests/suites/vlv/regression_test.py | 110 ++++++++++++++++++
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 5 +
ldap/servers/slapd/back-ldbm/vlv.c | 7 +-
3 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
index c8db94b19..1cc03c303 100644
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
@@ -1178,6 +1178,116 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
+def test_vlv_long_attribute_value(topology_st, request):
+ """
+ Test VLV with an entry containing a very long attribute value (2K).
+
+ :id: 99126fa4-003e-11f1-b7d6-c85309d5c3e3
+ :setup: Standalone instance.
+ :steps:
+ 1. Cleanup leftover from previous tests
+ 2. Create VLV search and index on cn attribute
+ 3. Reindex VLV
+ 4. Add an entry with a cn attribute having 2K character value
+ 5. Verify the entry was added successfully
+ 6. Perform a VLV search to ensure it still works
+ 7. Add another entry with a cn attribute having 2K character value
+ 8. Verify the entry was added successfully
+ 9. Perform a VLV search to ensure it still works
+ :expectedresults:
+ 1. Should Success.
+ 2. Should Success.
+ 3. Should Success.
+ 4. Should Success.
+ 5. Should Success.
+ 6. Should Success.
+ 7. Should Success.
+ 8. Should Success.
+ 9. Should Success.
+ """
+ inst = topology_st.standalone
+ reindex_task = Tasks(inst)
+
+ users_to_delete = []
+
+ def fin():
+ cleanup(inst)
+ # Clean the added users
+ for user in users_to_delete:
+ user.delete()
+
+ if not DEBUGGING:
+ request.addfinalizer(fin)
+
+ # Clean previous tests leftover
+ fin()
+
+ # Create VLV search and index
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
+ assert reindex_task.reindex(
+ suffix=DEFAULT_SUFFIX,
+ attrname=vlv_index.rdn,
+ args={TASK_WAIT: True},
+ vlv=True
+ ) == 0
+
+ # Add a few regular users first
+ add_users(inst, 10)
+
+ # Create a very long cn value (2K characters)
+ long_cn_value = 'a' * 2048 + '1'
+
+ # Add an entry with the long cn attribute
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ user_properties = {
+ 'uid': 'longcnuser1',
+ 'cn': long_cn_value,
+ 'sn': 'user1',
+ 'uidNumber': '99999',
+ 'gidNumber': '99999',
+ 'homeDirectory': '/home/longcnuser1'
+ }
+ user = users.create(properties=user_properties)
+ users_to_delete.append(user);
+
+ # Verify the entry was created and has the long cn value
+ entry = user.get_attr_vals_utf8('cn')
+ assert entry[0] == long_cn_value
+ log.info(f'Successfully created user with cn length: {len(entry[0])}')
+
+ # Perform VLV search to ensure VLV still works with long attribute values
+ conn = open_new_ldapi_conn(inst.serverid)
+ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
+ assert count > 0
+ log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
+
+ # Add another entry with the long cn attribute
+ long_cn_value = 'a' * 2048 + '2'
+
+ user_properties = {
+ 'uid': 'longcnuser2',
+ 'cn': long_cn_value,
+ 'sn': 'user2',
+ 'uidNumber': '99998',
+ 'gidNumber': '99998',
+ 'homeDirectory': '/home/longcnuser2'
+ }
+ user = users.create(properties=user_properties)
+ users_to_delete.append(user);
+
+ # Verify the entry was created and has the long cn value
+ entry = user.get_attr_vals_utf8('cn')
+ assert entry[0] == long_cn_value
+ log.info(f'Successfully created user with cn length: {len(entry[0])}')
+
+ # Perform VLV search to ensure VLV still works with long attribute values
+ conn = open_new_ldapi_conn(inst.serverid)
+ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
+ assert count > 1
+ log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
+
+
+
if __name__ == "__main__":
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
index c6e9f8b01..ffbd6609f 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
@@ -2138,10 +2138,15 @@ void *dbmdb_recno_cache_build(void *arg)
recno = 1;
}
while (rc == 0) {
+ struct ldbminfo *li = (struct ldbminfo *)rcctx->cursor->be->be_database->plg_private;
slapi_log_err(SLAPI_LOG_DEBUG, "dbmdb_recno_cache_build", "recno=%d\n", recno);
if (recno % RECNO_CACHE_INTERVAL == 1) {
/* Prepare the cache data */
len = sizeof(*rce) + data.mv_size + key.mv_size;
+ if (len > li->li_max_key_len) {
+ key.mv_size = li->li_max_key_len - data.mv_size - sizeof(*rce);
+ len = li->li_max_key_len;
+ }
rce = (dbmdb_recno_cache_elmt_t*)slapi_ch_malloc(len);
rce->len = len;
rce->recno = recno;
diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
index 18431799c..a1cd226f4 100644
--- a/ldap/servers/slapd/back-ldbm/vlv.c
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
@@ -866,6 +866,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
struct vlv_key *key = NULL;
dbi_val_t data = {0};
dblayer_private *priv = NULL;
+ size_t key_size_limit = li->li_max_key_len - sizeof(entry->ep_id);
slapi_pblock_get(pb, SLAPI_BACKEND, &be);
priv = (dblayer_private *)li->li_dblayer_private;
@@ -886,6 +887,10 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
return rc;
}
+ /* Truncate the key if it is too long */
+ if (key->key.size > key_size_limit) {
+ key->key.size = key_size_limit;
+ }
if (NULL != txn) {
db_txn = txn->back_txn_txn;
} else {
@@ -930,7 +935,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
if (txn && txn->back_special_handling_fn) {
rc = txn->back_special_handling_fn(be, BTXNACT_VLV_DEL, db, &key->key, &data, txn);
} else {
- rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, NULL);
+ rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, &data);
}
if (rc == 0) {
if (txn && txn->back_special_handling_fn) {
--
2.52.0

View File

@ -1,43 +0,0 @@
From 70d323d4d9ca4d1d682ab4a273178946922c6929 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 24 Jan 2025 11:10:45 +0100
Subject: [PATCH] Issue 6542 - RPM build errors on Fedora 42
Bug Description:
Fedora 42 has unified `/bin` and `/sbin`: https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin
https://docs.fedoraproject.org/en-US/packaging-guidelines/#_merged_file_system_layout
This change causes RPM build to fail with:
```
RPM build errors:
File not found: /builddir/build/BUILD/389-ds-base-3.1.2-build/BUILDROOT/usr/bin/openldap_to_ds
```
Fix Description:
Patch `setup.py.in` based on Fedora or RHEL version that support unified
`/bin` and `/sbin`.
Fixes: https://github.com/389ds/389-ds-base/issues/6542
Reviewed by: @mreynolds389, @droideck (Thanks!)
---
rpm/389-ds-base.spec.in | 3 +++
1 file changed, 3 insertions(+)
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 258d94698..44a158ce5 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -522,6 +522,9 @@ autoreconf -fiv
%if 0%{?rhel} > 7 || 0%{?fedora}
# lib389
+%if 0%{?fedora} >= 42 || 0%{?rhel} >= 11
+ sed -i "/prefix/s@sbin@bin@g" src/lib389/setup.py.in
+%endif
make src/lib389/setup.py
pushd ./src/lib389
%py3_build
--
2.52.0

File diff suppressed because it is too large Load Diff

View File

@ -1,785 +0,0 @@
From 7a45b50bf3d9cfc8f7e805379fcb39d5669d6c8b Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 5 Feb 2026 12:17:06 +0100
Subject: [PATCH] Issue 7223 - Revert index scan limits for system indexes
This reverts changes introduced by the following commits:
c6f458b42 Issue 7189 - DSBLE0007 generates incorrect remediation commands for scan limits
8b6b3a9f9 Issue 6966 - On large DB, unlimited IDL scan limit reduce the SRCH performance
Relates: https://github.com/389ds/389-ds-base/issues/7223
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
---
.../tests/suites/config/config_test.py | 27 +---
.../healthcheck/health_system_indexes_test.py | 135 +-----------------
.../paged_results/paged_results_test.py | 25 +---
ldap/servers/slapd/back-ldbm/back-ldbm.h | 1 -
ldap/servers/slapd/back-ldbm/index.c | 2 -
ldap/servers/slapd/back-ldbm/instance.c | 106 +++-----------
ldap/servers/slapd/back-ldbm/ldbm_config.c | 30 ----
ldap/servers/slapd/back-ldbm/ldbm_config.h | 1 -
.../slapd/back-ldbm/ldbm_index_config.c | 8 --
src/lib389/lib389/backend.py | 50 ++-----
src/lib389/lib389/cli_conf/backend.py | 20 ---
11 files changed, 41 insertions(+), 364 deletions(-)
diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
index b9ba684d9..6f11e7fc8 100644
--- a/dirsrvtests/tests/suites/config/config_test.py
+++ b/dirsrvtests/tests/suites/config/config_test.py
@@ -715,19 +715,17 @@ def test_ndn_cache_size_enforcement(topo, request):
request.addfinalizer(fin)
-def test_require_index(topo, request):
+def test_require_index(topo):
"""Validate that unindexed searches are rejected
:id: fb6e31f2-acc2-4e75-a195-5c356faeb803
:setup: Standalone instance
:steps:
1. Set "nsslapd-require-index" to "on"
- 2. ancestorid/idlscanlimit to 100
- 3. Test an unindexed search is rejected
+ 2. Test an unindexed search is rejected
:expectedresults:
1. Success
2. Success
- 3. Success
"""
# Set the config
@@ -738,10 +736,6 @@ def test_require_index(topo, request):
db_cfg = DatabaseConfig(topo.standalone)
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
- ancestorid_index = backend.get_index('ancestorid')
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
- topo.standalone.restart()
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
for i in range(101):
@@ -752,15 +746,10 @@ def test_require_index(topo, request):
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
raw_objects.filter("(description=test*)")
- def fin():
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
-
- request.addfinalizer(fin)
-
@pytest.mark.skipif(ds_is_older('1.4.2'), reason="The config setting only exists in 1.4.2 and higher")
-def test_require_internal_index(topo, request):
+def test_require_internal_index(topo):
"""Ensure internal operations require indexed attributes
:id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f
@@ -791,10 +780,6 @@ def test_require_internal_index(topo, request):
# Create a bunch of users
db_cfg = DatabaseConfig(topo.standalone)
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX)
- ancestorid_index = backend.get_index('ancestorid')
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
- topo.standalone.restart()
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
for i in range(102, 202):
users.create_test_user(uid=i)
@@ -819,12 +804,6 @@ def test_require_internal_index(topo, request):
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
user.delete()
- def fin():
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
-
- request.addfinalizer(fin)
-
-
def get_pstack(pid):
"""Get a pstack of the pid."""
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index 5eadf6283..61972d60c 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -171,8 +171,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
log.info("Re-add the parentId index")
backend = Backends(standalone).get("userRoot")
- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
@@ -260,8 +259,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer
log.info("Re-add the entryusn index")
backend = Backends(standalone).get("userRoot")
- backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"],
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
+ backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"])
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
@@ -405,132 +403,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-def test_missing_scanlimit(topology_st, log_buffering_enabled):
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit
-
- :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86
- :setup: Standalone instance
- :steps:
- 1. Create DS instance
- 2. Remove nsIndexIDListScanLimit from parentId index
- 3. Use healthcheck without --json option
- 4. Use healthcheck with --json option
- 5. Verify the remediation command has properly quoted scanlimit
- 6. Re-add the scanlimit
- 7. Use healthcheck without --json option
- 8. Use healthcheck with --json option
- :expectedresults:
- 1. Success
- 2. Success
- 3. healthcheck reports DSBLE0007 code and related details
- 4. healthcheck reports DSBLE0007 code and related details
- 5. The scanlimit value is quoted in the remediation command
- 6. Success
- 7. healthcheck reports no issues found
- 8. healthcheck reports no issues found
- """
-
- RET_CODE = "DSBLE0007"
- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
-
- standalone = topology_st.standalone
-
- log.info("Remove nsIndexIDListScanLimit from parentId index")
- parentid_index = Index(standalone, PARENTID_DN)
- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
-
- # Verify the remediation command has properly quoted scanlimit
- args = FakeArgs()
- args.instance = standalone.serverid
- args.verbose = standalone.verbose
- args.list_errors = False
- args.list_checks = False
- args.exclude_check = []
- args.check = ["backends"]
- args.dry_run = False
- args.json = False
- health_check_run(standalone, topology_st.logcap.log, args)
- # Check that the scanlimit is quoted in the output
- assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"')
- log.info("Verified scanlimit is properly quoted in remediation command")
- topology_st.logcap.flush()
-
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
-
- log.info("Re-add the nsIndexIDListScanLimit")
- parentid_index = Index(standalone, PARENTID_DN)
- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-
-
-def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled):
- """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing
-
- :id: af8214ad-5e4c-422a-8f74-3e99227551df
- :setup: Standalone instance
- :steps:
- 1. Create DS instance
- 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index
- 3. Use healthcheck and verify a single combined command is generated
- 4. Re-add the matching rule and scanlimit
- 5. Use healthcheck without --json option
- 6. Use healthcheck with --json option
- :expectedresults:
- 1. Success
- 2. Success
- 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit
- 4. Success
- 5. healthcheck reports no issues found
- 6. healthcheck reports no issues found
- """
-
- RET_CODE = "DSBLE0007"
- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
-
- standalone = topology_st.standalone
-
- log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index")
- parentid_index = Index(standalone, PARENTID_DN)
- parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-
- # Run healthcheck and verify combined command
- args = FakeArgs()
- args.instance = standalone.serverid
- args.verbose = standalone.verbose
- args.list_errors = False
- args.list_checks = False
- args.exclude_check = []
- args.check = ["backends"]
- args.dry_run = False
- args.json = False
- health_check_run(standalone, topology_st.logcap.log, args)
-
- # Verify DSBLE0007 is reported
- assert topology_st.logcap.contains(RET_CODE)
- log.info("healthcheck returned code: %s" % RET_CODE)
-
- # Verify a single combined command is generated with both --add-mr and --add-scanlimit
- assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"')
- log.info("Verified combined command with both --add-mr and --add-scanlimit")
-
- topology_st.logcap.flush()
-
- log.info("Re-add the integerOrderingMatch matching rule and scanlimit")
- parentid_index = Index(standalone, PARENTID_DN)
- parentid_index.add("nsMatchingRule", "integerOrderingMatch")
- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE)
-
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-
-
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
@@ -571,8 +443,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
log.info("Re-add the missing system indexes")
backend = Backends(standalone).get("userRoot")
- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"],
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
backend.add_index("nsuniqueid", ["eq"])
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
index 61d6702da..1bb94b53a 100644
--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py
+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
@@ -306,19 +306,19 @@ def test_search_success(topology_st, create_user, page_size, users_num):
del_users(users_list)
-@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err, restart", [
+@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err", [
(50, 200, 'cn=config,%s' % DN_LDBM, 'nsslapd-idlistscanlimit', '100',
- ldap.UNWILLING_TO_PERFORM, True),
+ ldap.UNWILLING_TO_PERFORM),
(5, 15, DN_CONFIG, 'nsslapd-timelimit', '20',
- ldap.UNAVAILABLE_CRITICAL_EXTENSION, False),
+ ldap.UNAVAILABLE_CRITICAL_EXTENSION),
(21, 50, DN_CONFIG, 'nsslapd-sizelimit', '20',
- ldap.SIZELIMIT_EXCEEDED, False),
+ ldap.SIZELIMIT_EXCEEDED),
(21, 50, DN_CONFIG, 'nsslapd-pagedsizelimit', '5',
- ldap.SIZELIMIT_EXCEEDED, False),
+ ldap.SIZELIMIT_EXCEEDED),
(5, 50, 'cn=config,%s' % DN_LDBM, 'nsslapd-lookthroughlimit', '20',
- ldap.ADMINLIMIT_EXCEEDED, False)])
+ ldap.ADMINLIMIT_EXCEEDED)])
def test_search_limits_fail(topology_st, create_user, page_size, users_num,
- suffix, attr_name, attr_value, expected_err, restart):
+ suffix, attr_name, attr_value, expected_err):
"""Verify that search with a simple paged results control
throws expected exceptoins when corresponding limits are
exceeded.
@@ -341,15 +341,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
users_list = add_users(topology_st, users_num, DEFAULT_SUFFIX)
attr_value_bck = change_conf_attr(topology_st, suffix, attr_name, attr_value)
- ancestorid_index = None
- if attr_name == 'nsslapd-idlistscanlimit':
- backend = Backends(topology_st.standalone).get_backend(DEFAULT_SUFFIX)
- ancestorid_index = backend.get_index('ancestorid')
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND"))
-
- if (restart):
- log.info('Instance restarted')
- topology_st.standalone.restart()
conf_param_dict = {attr_name: attr_value}
search_flt = r'(uid=test*)'
searchreq_attrlist = ['dn', 'sn']
@@ -402,8 +393,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
else:
break
finally:
- if ancestorid_index:
- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND"))
del_users(users_list)
change_conf_attr(topology_st, suffix, attr_name, attr_value_bck)
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
index 5e4988782..a4993208c 100644
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
@@ -561,7 +561,6 @@ struct ldbminfo
int li_mode;
int li_lookthroughlimit;
int li_allidsthreshold;
- int li_system_allidsthreshold;
char *li_directory;
int li_reslimit_lookthrough_handle;
uint64_t li_dbcachesize;
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
index e06a8ee5f..90129b682 100644
--- a/ldap/servers/slapd/back-ldbm/index.c
+++ b/ldap/servers/slapd/back-ldbm/index.c
@@ -1007,8 +1007,6 @@ index_read_ext_allids(
}
if (pb) {
slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and);
- } else if (strcasecmp(type, LDBM_ANCESTORID_STR) == 0) {
- is_and = 1;
}
ai_flags = is_and ? INDEX_ALLIDS_FLAG_AND : 0;
/* the caller can pass in a value of 0 - just ignore those - but if the index
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
index 23bd70243..f9a546661 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -16,7 +16,7 @@
/* Forward declarations */
static void ldbm_instance_destructor(void **arg);
-Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr, char *scanlimit);
+Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr);
/* Creates and initializes a new ldbm_instance structure.
@@ -127,7 +127,7 @@ done:
* Take a bunch of strings, and create a index config entry
*/
Slapi_Entry *
-ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr, char *scanlimit)
+ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr)
{
Slapi_Entry *e = slapi_entry_alloc();
struct berval *vals[2];
@@ -168,11 +168,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
slapi_entry_add_values(e, "nsMatchingRule", vals);
}
- if (scanlimit) {
- val.bv_val = scanlimit;
- val.bv_len = strlen(scanlimit);
- slapi_entry_add_values(e, "nsIndexIDListScanLimit", vals);
- }
return e;
}
@@ -185,60 +180,8 @@ ldbm_instance_create_default_indexes(backend *be)
{
Slapi_Entry *e;
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
/* write the dse file only on the final index */
int flags = LDBM_INSTANCE_CONFIG_DONT_WRITE;
- char *ancestorid_indexes_limit = NULL;
- char *parentid_indexes_limit = NULL;
- struct attrinfo *ai = NULL;
- int index_already_configured = 0;
- struct index_idlistsizeinfo *iter;
- int cookie;
- int limit;
-
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
- if (ai && ai->ai_idlistinfo) {
- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
- if (iter) {
- limit = iter->ai_idlistsizelimit;
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
- "set ancestorid limit to %d from attribute index\n",
- limit);
- } else {
- limit = li->li_system_allidsthreshold;
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
- "set ancestorid limit to %d from default (fail to read limit)\n",
- limit);
- }
- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
- } else {
- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
- "set ancestorid limit to %d from default (no attribute or limit)\n",
- li->li_system_allidsthreshold);
- }
-
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
- if (ai && ai->ai_idlistinfo) {
- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie);
- if (iter) {
- limit = iter->ai_idlistsizelimit;
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
- "set parentid limit to %d from attribute index\n",
- limit);
- } else {
- limit = li->li_system_allidsthreshold;
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
- "set parentid limit to %d from default (fail to read limit)\n",
- limit);
- }
- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit);
- } else {
- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold);
- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes",
- "set parentid limit to %d from default (no attribute or limit)\n",
- li->li_system_allidsthreshold);
- }
/*
* Always index (entrydn or entryrdn), parentid, objectclass,
@@ -247,53 +190,47 @@ ldbm_instance_create_default_indexes(backend *be)
* ACL routines.
*/
if (entryrdn_get_switch()) { /* subtree-rename: on */
- e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
} else {
- e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
}
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
- /* Check if the attrinfo is actually for parentid, not a fallback to .default */
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0);
- if (!index_already_configured) {
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
- ldbm_instance_config_add_index_entry(inst, e, flags);
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
- slapi_entry_free(e);
- }
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
+ ldbm_instance_config_add_index_entry(inst, e, flags);
+ slapi_entry_free(e);
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
/* For MMR, we need this attribute (to replace use of dncomp in delete). */
- e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
/* write the dse file only on the final index */
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
/* ldbm_instance_config_add_index_entry(inst, 2, argv); */
- e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0);
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
slapi_entry_free(e);
@@ -302,20 +239,11 @@ ldbm_instance_create_default_indexes(backend *be)
* ancestorid is special, there is actually no such attr type
* but we still want to use the attr index file APIs.
*/
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
- /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
- if (!index_already_configured) {
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
- ldbm_instance_config_add_index_entry(inst, e, flags);
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
- slapi_entry_free(e);
- }
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
+ slapi_entry_free(e);
}
- slapi_ch_free_string(&ancestorid_indexes_limit);
- slapi_ch_free_string(&parentid_indexes_limit);
-
return 0;
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
index d8ffc4479..5cce0dbd3 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
@@ -386,35 +386,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
return retval;
}
-static void *
-ldbm_config_system_allidsthreshold_get(void *arg)
-{
- struct ldbminfo *li = (struct ldbminfo *)arg;
-
- return (void *)((uintptr_t)(li->li_system_allidsthreshold));
-}
-
-static int
-ldbm_config_system_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply)
-{
- struct ldbminfo *li = (struct ldbminfo *)arg;
- int retval = LDAP_SUCCESS;
- int val = (int)((uintptr_t)value);
-
- /* Do whatever we can to make sure the data is ok. */
-
- /* Catch attempts to configure a stupidly low ancestorid allidsthreshold */
- if ((val > -1) && (val < 5000)) {
- val = 5000;
- }
-
- if (apply) {
- li->li_system_allidsthreshold = val;
- }
-
- return retval;
-}
-
static void *
ldbm_config_pagedallidsthreshold_get(void *arg)
{
@@ -1133,7 +1104,6 @@ static config_info ldbm_config[] = {
{CONFIG_LOOKTHROUGHLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_lookthroughlimit_get, &ldbm_config_lookthroughlimit_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_MODE, CONFIG_TYPE_INT_OCTAL, "0600", &ldbm_config_mode_get, &ldbm_config_mode_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_IDLISTSCANLIMIT, CONFIG_TYPE_INT, "2147483646", &ldbm_config_allidsthreshold_get, &ldbm_config_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
- {CONFIG_SYSTEMIDLISTSCANLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_system_allidsthreshold_get, &ldbm_config_system_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_DIRECTORY, CONFIG_TYPE_STRING, "", &ldbm_config_directory_get, &ldbm_config_directory_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE | CONFIG_FLAG_SKIP_DEFAULT_SETTING},
{CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &ldbm_config_maxpassbeforemerge_get, &ldbm_config_maxpassbeforemerge_set, 0},
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
index 30e3477ed..e1f05d7a2 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
@@ -60,7 +60,6 @@ struct config_info
#define CONFIG_RANGELOOKTHROUGHLIMIT "nsslapd-rangelookthroughlimit"
#define CONFIG_PAGEDLOOKTHROUGHLIMIT "nsslapd-pagedlookthroughlimit"
#define CONFIG_IDLISTSCANLIMIT "nsslapd-idlistscanlimit"
-#define CONFIG_SYSTEMIDLISTSCANLIMIT "nsslapd-systemidlistscanlimit"
#define CONFIG_PAGEDIDLISTSCANLIMIT "nsslapd-pagedidlistscanlimit"
#define CONFIG_DIRECTORY "nsslapd-directory"
#define CONFIG_MODE "nsslapd-mode"
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
index bae2a64b9..38e7368e1 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c
@@ -384,14 +384,6 @@ ldbm_instance_config_add_index_entry(
}
}
- /* get nsIndexIDListScanLimit and its values, and add them */
- if (0 == slapi_entry_attr_find(e, "nsIndexIDListScanLimit", &attr)) {
- for (j = slapi_attr_first_value(attr, &sval); j != -1; j = slapi_attr_next_value(attr, j, &sval)) {
- attrValue = slapi_value_get_berval(sval);
- eBuf = PR_sprintf_append(eBuf, "nsIndexIDListScanLimit: %s\n", attrValue->bv_val);
- }
- }
-
ldbm_config_add_dse_entry(li, eBuf, flags);
if (eBuf) {
PR_smprintf_free(eBuf);
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index d3c9ccf35..1d9be4683 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -615,10 +615,11 @@ class Backend(DSLdapObject):
indexes = self.get_indexes()
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
+ # Note: entryrdn and ancestorid are internal system indexes that are not
+ # exposed in cn=config - they are managed internally by the server.
+ # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
expected_system_indexes = {
- 'entryrdn': {'types': ['subtree'], 'matching_rule': None},
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
- 'ancestorid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'},
+ 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
'objectClass': {'types': ['eq'], 'matching_rule': None},
'aci': {'types': ['pres'], 'matching_rule': None},
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
@@ -675,17 +676,14 @@ class Backend(DSLdapObject):
# Generate remediation command
index_types = ' '.join([f"--index-type {t}" for t in expected_config['types']])
cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
- if expected_config.get('matching_rule'):
+ if expected_config['matching_rule']:
cmd += f" --matching-rule {expected_config['matching_rule']}"
- if expected_config.get('scanlimit'):
- cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\""
remediation_commands.append(cmd)
reindex_attrs.add(attr_name) # New index needs reindexing
else:
# Index exists, check configuration
actual_types = index.get_attr_vals_utf8('nsIndexType') or []
actual_mrs = index.get_attr_vals_utf8('nsMatchingRule') or []
- actual_scanlimit = index.get_attr_vals_utf8('nsIndexIDListScanLimit') or []
# Normalize to lowercase for comparison
actual_types = [t.lower() for t in actual_types]
@@ -700,31 +698,16 @@ class Backend(DSLdapObject):
remediation_commands.append(cmd)
reindex_attrs.add(attr_name)
- # Check matching rules and scanlimit together to generate a single combined command
+ # Check matching rules
expected_mr = expected_config.get('matching_rule')
- expected_scanlimit = expected_config.get('scanlimit')
-
- missing_mr = False
if expected_mr:
actual_mrs_lower = [mr.lower() for mr in actual_mrs]
if expected_mr.lower() not in actual_mrs_lower:
discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}")
- missing_mr = True
-
- missing_scanlimit = False
- if expected_scanlimit and (len(actual_scanlimit) == 0):
- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}")
- missing_scanlimit = True
-
- # Generate a single combined command for all missing items
- if missing_mr or missing_scanlimit:
- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}"
- if missing_mr:
- cmd += f" --add-mr {expected_mr}"
- if missing_scanlimit:
- cmd += f" --add-scanlimit \"{expected_scanlimit}\""
- remediation_commands.append(cmd)
- reindex_attrs.add(attr_name)
+ # Add the missing matching rule
+ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}"
+ remediation_commands.append(cmd)
+ reindex_attrs.add(attr_name)
except Exception as e:
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
@@ -963,13 +946,12 @@ class Backend(DSLdapObject):
return
raise ValueError("Can not delete index because it does not exist")
- def add_index(self, attr_name, types, matching_rules=None, idlistscanlimit=None, reindex=False):
+ def add_index(self, attr_name, types, matching_rules=None, reindex=False):
""" Add an index.
:param attr_name - name of the attribute to index
:param types - a List of index types(eq, pres, sub, approx)
:param matching_rules - a List of matching rules for the index
- :param idlistscanlimit - a List of fine grain definitions for scanning limit
:param reindex - If set to True then index the attribute after creating it.
"""
@@ -999,15 +981,6 @@ class Backend(DSLdapObject):
# Only add if there are actually rules present in the list.
if len(mrs) > 0:
props['nsMatchingRule'] = mrs
-
- if idlistscanlimit is not None:
- scanlimits = []
- for scanlimit in idlistscanlimit:
- scanlimits.append(scanlimit)
- # Only add if there are actually limits in the list.
- if len(scanlimits) > 0:
- props['nsIndexIDListScanLimit'] = scanlimits
-
new_index.create(properties=props, basedn="cn=index," + self._dn)
if reindex:
@@ -1314,7 +1287,6 @@ class DatabaseConfig(DSLdapObject):
'nsslapd-lookthroughlimit',
'nsslapd-mode',
'nsslapd-idlistscanlimit',
- 'nsslapd-systemidlistscanlimit',
'nsslapd-directory',
'nsslapd-import-cachesize',
'nsslapd-idl-switch',
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
index dcf57d006..80008d22d 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -39,7 +39,6 @@ arg_to_attr = {
'mode': 'nsslapd-mode',
'state': 'nsslapd-state',
'idlistscanlimit': 'nsslapd-idlistscanlimit',
- 'systemidlistscanlimit': 'nsslapd-systemidlistscanlimit',
'directory': 'nsslapd-directory',
'dbcachesize': 'nsslapd-dbcachesize',
'logdirectory': 'nsslapd-db-logdirectory',
@@ -626,21 +625,6 @@ def backend_set_index(inst, basedn, log, args):
except ldap.NO_SUCH_ATTRIBUTE:
raise ValueError('Can not delete matching rule type because it does not exist')
- if args.replace_scanlimit is not None:
- for replace_scanlimit in args.replace_scanlimit:
- index.replace('nsIndexIDListScanLimit', replace_scanlimit)
-
- if args.add_scanlimit is not None:
- for add_scanlimit in args.add_scanlimit:
- index.add('nsIndexIDListScanLimit', add_scanlimit)
-
- if args.del_scanlimit is not None:
- for del_scanlimit in args.del_scanlimit:
- try:
- index.remove('nsIndexIDListScanLimit', del_scanlimit)
- except ldap.NO_SUCH_ATTRIBUTE:
- raise ValueError('Can not delete a fine grain limit definition because it does not exist')
-
if args.reindex:
be.reindex(attrs=[args.attr])
log.info("Index successfully updated")
@@ -962,9 +946,6 @@ def create_parser(subparsers):
edit_index_parser.add_argument('--del-type', action='append', help='Removes an index type from the index: (eq, sub, pres, or approx)')
edit_index_parser.add_argument('--add-mr', action='append', help='Adds a matching-rule to the index')
edit_index_parser.add_argument('--del-mr', action='append', help='Removes a matching-rule from the index')
- edit_index_parser.add_argument('--add-scanlimit', action='append', help='Adds a fine grain limit definiton to the index')
- edit_index_parser.add_argument('--replace-scanlimit', action='append', help='Replaces a fine grain limit definiton to the index')
- edit_index_parser.add_argument('--del-scanlimit', action='append', help='Removes a fine grain limit definiton to the index')
edit_index_parser.add_argument('--reindex', action='store_true', help='Re-indexes the database after editing the index')
edit_index_parser.add_argument('be_name', help='The backend name or suffix')
@@ -1091,7 +1072,6 @@ def create_parser(subparsers):
'will check when examining candidate entries in response to a search request')
set_db_config_parser.add_argument('--mode', help='Specifies the permissions used for newly created index files')
set_db_config_parser.add_argument('--idlistscanlimit', help='Specifies the number of entry IDs that are searched during a search operation')
- set_db_config_parser.add_argument('--systemidlistscanlimit', help='Specifies the number of entry IDs that are fetch from ancestorid/parentid indexes')
set_db_config_parser.add_argument('--directory', help='Specifies absolute path to database instance')
set_db_config_parser.add_argument('--dbcachesize', help='Specifies the database index cache size in bytes')
set_db_config_parser.add_argument('--logdirectory', help='Specifies the path to the directory that contains the database transaction logs')
--
2.52.0

View File

@ -1,212 +0,0 @@
From a58212943da2604091e7d5a20829ab54970d41c9 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 5 Feb 2026 12:17:06 +0100
Subject: [PATCH] Issue 7223 - Add upgrade function to remove
nsIndexIDListScanLimit from parentid
Description:
Add `upgrade_remove_index_scanlimit()` function that removes the
nsIndexIDListScanLimit attribute from parentid index configuration
if present.
This attribute was incorrectly added by a previous version and can
cause issues with index configuration. The upgrade function runs
automatically on server startup and removes the attribute if found.
Relates: https://github.com/389ds/389-ds-base/issues/7223
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
---
.../healthcheck/health_system_indexes_test.py | 52 +++++++++
ldap/servers/slapd/upgrade.c | 105 ++++++++++++++++++
2 files changed, 157 insertions(+)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index 61972d60c..b0d7a99ec 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -450,6 +450,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
+def test_upgrade_removes_parentid_scanlimit(topology_st):
+ """Check if upgrade function removes nsIndexIDListScanLimit from parentid index
+
+ :id: 2808886e-c1c1-441d-b3a3-299c4ef1ab4a
+ :setup: Standalone instance
+ :steps:
+ 1. Create DS instance
+ 2. Stop the server
+ 3. Use DSEldif to add nsIndexIDListScanLimit to parentid index
+ 4. Start the server (triggers upgrade)
+ 5. Verify nsIndexIDListScanLimit is removed from parentid index
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. nsIndexIDListScanLimit is no longer present
+ """
+ from lib389.dseldif import DSEldif
+
+ standalone = topology_st.standalone
+ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
+ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND"
+
+ log.info("Stop the server")
+ standalone.stop()
+
+ log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif")
+ dse_ldif = DSEldif(standalone)
+ dse_ldif.add(PARENTID_DN, "nsIndexIDListScanLimit", SCANLIMIT_VALUE)
+
+ # Verify it was added
+ scanlimit = dse_ldif.get(PARENTID_DN, "nsIndexIDListScanLimit")
+ assert scanlimit is not None, "Failed to add nsIndexIDListScanLimit"
+ log.info(f"Added nsIndexIDListScanLimit: {scanlimit}")
+
+ log.info("Start the server (triggers upgrade)")
+ standalone.start()
+
+ log.info("Verify nsIndexIDListScanLimit was removed by upgrade")
+ # Check via LDAP - the upgrade should have removed it
+ parentid_index = Index(standalone, PARENTID_DN)
+ scanlimit_after = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
+ log.info(f"nsIndexIDListScanLimit after upgrade: {scanlimit_after}")
+
+ # The upgrade function should have removed nsIndexIDListScanLimit
+ assert not scanlimit_after, \
+ f"nsIndexIDListScanLimit should have been removed but found: {scanlimit_after}"
+
+ log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
+
+
if __name__ == "__main__":
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
index 43906c1af..adfec63de 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -279,6 +279,107 @@ upgrade_205_fixup_repl_dep(void)
return UPGRADE_SUCCESS;
}
+/*
+ * Remove nsIndexIDListScanLimit from parentid index configuration.
+ *
+ * This attribute was incorrectly added by a previous version and can
+ * cause issues with index configuration. Remove it if present.
+ */
+static upgrade_status
+upgrade_remove_index_scanlimit(void)
+{
+ struct slapi_pblock *pb = slapi_pblock_new();
+ Slapi_Entry **backends = NULL;
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
+ const char *be_filter = "(objectclass=nsBackendInstance)";
+ const char *attrs_to_check[] = {"parentid", NULL};
+ upgrade_status uresult = UPGRADE_SUCCESS;
+
+ /* Search for all backend instances */
+ slapi_search_internal_set_pb(
+ pb, be_base_dn,
+ LDAP_SCOPE_ONELEVEL,
+ be_filter, NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
+
+ if (backends) {
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
+ if (!be_dn || !be_name) {
+ continue;
+ }
+
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
+ const char *attr_name = attrs_to_check[attr_idx];
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
+ Slapi_Entry **idx_entries = NULL;
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
+ attr_name, be_dn);
+ char *idx_filter = "(objectclass=nsIndex)";
+
+ if (!idx_dn) {
+ slapi_pblock_destroy(idx_pb);
+ continue;
+ }
+
+ slapi_search_internal_set_pb(
+ idx_pb, idx_dn,
+ LDAP_SCOPE_BASE,
+ idx_filter, NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(idx_pb);
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
+
+ if (idx_entries && idx_entries[0]) {
+ /* Check if nsIndexIDListScanLimit is present */
+ if (slapi_entry_attr_get_ref(idx_entries[0], "nsIndexIDListScanLimit") != NULL) {
+ /* Remove nsIndexIDListScanLimit */
+ Slapi_PBlock *mod_pb = slapi_pblock_new();
+ Slapi_Mods smods;
+ int rc;
+
+ slapi_mods_init(&smods, 1);
+ slapi_mods_add(&smods, LDAP_MOD_DELETE, "nsIndexIDListScanLimit", 0, NULL);
+
+ slapi_modify_internal_set_pb(
+ mod_pb, idx_dn,
+ slapi_mods_get_ldapmods_byref(&smods),
+ NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_modify_internal_pb(mod_pb);
+ slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ if (rc == LDAP_SUCCESS) {
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_index_scanlimit",
+ "Removed 'nsIndexIDListScanLimit' from index '%s' in backend '%s'\n",
+ attr_name, be_name);
+ } else if (rc != LDAP_NO_SUCH_ATTRIBUTE) {
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_index_scanlimit",
+ "Failed to remove 'nsIndexIDListScanLimit' from index '%s' in backend '%s': error %d\n",
+ attr_name, be_name, rc);
+ }
+
+ slapi_mods_done(&smods);
+ slapi_pblock_destroy(mod_pb);
+ }
+ }
+
+ slapi_ch_free_string(&idx_dn);
+ slapi_free_search_results_internal(idx_pb);
+ slapi_pblock_destroy(idx_pb);
+ }
+ }
+ }
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return uresult;
+}
+
/*
* Check if parentid/ancestorid indexes are missing the integerOrderingMatch
* matching rule.
@@ -407,6 +508,10 @@ upgrade_server(void)
return UPGRADE_FAILURE;
}
+ if (upgrade_remove_index_scanlimit() != UPGRADE_SUCCESS) {
+ return UPGRADE_FAILURE;
+ }
+
if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
return UPGRADE_FAILURE;
}
--
2.52.0

View File

@ -1,313 +0,0 @@
From 9a9446b9bafe25eacf97039e66d6ef19d366315c Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 5 Feb 2026 12:17:06 +0100
Subject: [PATCH] Issue 7223 - Add upgrade function to remove ancestorid index
config entry
Description:
Add `upgrade_remove_ancestorid_index_config()` function that removes:
* ancestorid from `cn=default indexes`
* ancestorid index config entries from each backend's `cn=index`
Also remove ancestorid index configuration from template-dse.ldif.
Relates: https://github.com/389ds/389-ds-base/issues/7223
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
---
.../healthcheck/health_system_indexes_test.py | 85 +++++++++++
ldap/ldif/template-dse.ldif.in | 8 --
ldap/servers/slapd/upgrade.c | 133 +++++++++++++++++-
3 files changed, 214 insertions(+), 12 deletions(-)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index b0d7a99ec..4b0c58835 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -501,6 +501,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st):
log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
+ # Verify idempotency - restart again and ensure no errors
+ log.info("Restart server again to verify idempotency (no errors on second run)")
+ standalone.restart()
+ # Verify the attribute is still absent
+ scanlimit_after_second = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit")
+ assert not scanlimit_after_second, \
+ f"nsIndexIDListScanLimit should still be absent after second restart but found: {scanlimit_after_second}"
+ log.info("Idempotency verified - no issues on second restart")
+
+
+def test_upgrade_removes_ancestorid_index_config(topology_st):
+ """Check if upgrade function removes ancestorid index config entry
+
+ :id: 3f3d6e9b-75ac-4f0d-b2ce-7204e6eacd0a
+ :setup: Standalone instance
+ :steps:
+ 1. Create DS instance
+ 2. Stop the server
+ 3. Use DSEldif to add an ancestorid index config entry
+ 4. Start the server (triggers upgrade)
+ 5. Verify ancestorid index config entry is removed
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. ancestorid index config entry is no longer present
+ """
+ from lib389.dseldif import DSEldif
+
+ standalone = topology_st.standalone
+ ANCESTORID_DN = "cn=ancestorid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
+
+ log.info("Stop the server")
+ standalone.stop()
+
+ log.info("Add ancestorid index config entry using DSEldif")
+ dse_ldif = DSEldif(standalone)
+
+ # Create a fake ancestorid index entry
+ ancestorid_entry = [
+ "dn: {}\n".format(ANCESTORID_DN),
+ "objectClass: top\n",
+ "objectClass: nsIndex\n",
+ "cn: ancestorid\n",
+ "nsSystemIndex: true\n",
+ "nsIndexType: eq\n",
+ "nsMatchingRule: integerOrderingMatch\n",
+ "\n"
+ ]
+ dse_ldif.add_entry(ancestorid_entry)
+
+ # Verify it was added by re-reading dse.ldif
+ dse_ldif2 = DSEldif(standalone)
+ cn_value = dse_ldif2.get(ANCESTORID_DN, "cn")
+ assert cn_value is not None, "Failed to add ancestorid index config entry"
+ log.info(f"Added ancestorid index entry with cn: {cn_value}")
+
+ log.info("Start the server (triggers upgrade)")
+ standalone.start()
+
+ log.info("Verify ancestorid index config entry was removed by upgrade")
+ # Check via LDAP - the upgrade should have removed the entry
+ try:
+ ancestorid_index = Index(standalone, ANCESTORID_DN)
+ # If we can get the entry, it wasn't removed - this is a failure
+ cn_after = ancestorid_index.get_attr_vals_utf8("cn")
+ assert False, f"ancestorid index config entry should have been removed but still exists: {cn_after}"
+ except Exception as e:
+ # Entry should not exist - this is expected
+ log.info(f"ancestorid index config entry correctly removed (got exception: {e})")
+
+ log.info("Upgrade successfully removed ancestorid index config entry")
+
+ # Verify idempotency - restart again and ensure no errors
+ log.info("Restart server again to verify idempotency (no errors on second run)")
+ standalone.restart()
+ # Verify the entry is still absent
+ try:
+ ancestorid_index = Index(standalone, ANCESTORID_DN)
+ cn_after_second = ancestorid_index.get_attr_vals_utf8("cn")
+ assert False, f"ancestorid index config entry should still be absent after second restart but found: {cn_after_second}"
+ except Exception as e:
+ log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})")
+
if __name__ == "__main__":
# Run isolated
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
index 6f97d492b..70e7a85ac 100644
--- a/ldap/ldif/template-dse.ldif.in
+++ b/ldap/ldif/template-dse.ldif.in
@@ -990,14 +990,6 @@ cn: aci
nssystemindex: true
nsindextype: pres
-dn: cn=ancestorid,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
-objectclass: top
-objectclass: nsIndex
-cn: ancestorid
-nssystemindex: true
-nsindextype: eq
-nsmatchingrule: integerOrderingMatch
-
dn: cn=cn,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config
objectclass: top
objectclass: nsIndex
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
index adfec63de..d9156cae9 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -380,6 +380,126 @@ upgrade_remove_index_scanlimit(void)
return uresult;
}
+/*
+ * Remove ancestorid index configuration entry if present.
+ *
+ * The ancestorid index is special - it has no corresponding attribute type
+ * and should not have a DSE config entry. If an entry exists, remove it.
+ *
+ * This function removes:
+ * 1. The ancestorid entry from cn=default indexes (to prevent re-creation on startup)
+ * 2. The ancestorid entry from each backend's cn=index (if it exists)
+ */
+static upgrade_status
+upgrade_remove_ancestorid_index_config(void)
+{
+ struct slapi_pblock *pb = slapi_pblock_new();
+ Slapi_Entry **backends = NULL;
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
+ const char *be_filter = "(objectclass=nsBackendInstance)";
+ upgrade_status uresult = UPGRADE_SUCCESS;
+ int rc;
+
+ /*
+ * First, remove ancestorid from cn=default indexes to prevent
+ * ldbm_instance_create_default_user_indexes() from re-creating it.
+ */
+ {
+ Slapi_PBlock *def_pb = slapi_pblock_new();
+ char *def_idx_dn = slapi_create_dn_string(
+ "cn=ancestorid,cn=default indexes,cn=config,%s", be_base_dn);
+
+ if (def_idx_dn) {
+ slapi_delete_internal_set_pb(
+ def_pb, def_idx_dn, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_delete_internal_pb(def_pb);
+ slapi_pblock_get(def_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ if (rc == LDAP_SUCCESS) {
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
+ "Removed 'ancestorid' from default indexes.\n");
+ } else if (rc != LDAP_NO_SUCH_OBJECT) {
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
+ "Failed to remove 'ancestorid' from default indexes: error %d\n", rc);
+ }
+
+ slapi_ch_free_string(&def_idx_dn);
+ }
+ slapi_pblock_destroy(def_pb);
+ }
+
+ /* Search for all backend instances */
+ slapi_search_internal_set_pb(
+ pb, be_base_dn,
+ LDAP_SCOPE_ONELEVEL,
+ be_filter, NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
+
+ if (backends) {
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
+ if (!be_dn || !be_name) {
+ continue;
+ }
+
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
+ Slapi_Entry **idx_entries = NULL;
+ char *idx_dn = slapi_create_dn_string("cn=ancestorid,cn=index,%s",
+ be_dn);
+ char *idx_filter = "(objectclass=nsIndex)";
+
+ if (!idx_dn) {
+ slapi_pblock_destroy(idx_pb);
+ continue;
+ }
+
+ slapi_search_internal_set_pb(
+ idx_pb, idx_dn,
+ LDAP_SCOPE_BASE,
+ idx_filter, NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(idx_pb);
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
+
+ if (idx_entries && idx_entries[0]) {
+ /* ancestorid index entry exists - delete it */
+ Slapi_PBlock *del_pb = slapi_pblock_new();
+
+ slapi_delete_internal_set_pb(
+ del_pb, idx_dn, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_delete_internal_pb(del_pb);
+ slapi_pblock_get(del_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ if (rc == LDAP_SUCCESS) {
+ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config",
+ "Removed 'ancestorid' index config entry in backend '%s'.\n",
+ be_name);
+ } else if (rc != LDAP_NO_SUCH_OBJECT) {
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config",
+ "Failed to remove 'ancestorid' index config entry in backend '%s': error %d\n",
+ be_name, rc);
+ }
+
+ slapi_pblock_destroy(del_pb);
+ }
+
+ slapi_ch_free_string(&idx_dn);
+ slapi_free_search_results_internal(idx_pb);
+ slapi_pblock_destroy(idx_pb);
+ }
+ }
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return uresult;
+}
+
/*
* Check if parentid/ancestorid indexes are missing the integerOrderingMatch
* matching rule.
@@ -394,7 +514,7 @@ upgrade_check_id_index_matching_rule(void)
Slapi_Entry **backends = NULL;
const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
const char *be_filter = "(objectclass=nsBackendInstance)";
- const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
+ const char *attrs_to_check[] = {"parentid", NULL};
upgrade_status uresult = UPGRADE_SUCCESS;
/* Search for all backend instances */
@@ -408,8 +528,9 @@ upgrade_check_id_index_matching_rule(void)
if (backends) {
for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
+ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
- if (!be_name) {
+ if (!be_dn || !be_name) {
continue;
}
@@ -418,8 +539,8 @@ upgrade_check_id_index_matching_rule(void)
const char *attr_name = attrs_to_check[attr_idx];
struct slapi_pblock *idx_pb = slapi_pblock_new();
Slapi_Entry **idx_entries = NULL;
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
- attr_name, be_name, be_base_dn);
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
+ attr_name, be_dn);
char *idx_filter = "(objectclass=nsIndex)";
PRBool has_matching_rule = PR_FALSE;
@@ -512,6 +633,10 @@ upgrade_server(void)
return UPGRADE_FAILURE;
}
+ if (upgrade_remove_ancestorid_index_config() != UPGRADE_SUCCESS) {
+ return UPGRADE_FAILURE;
+ }
+
if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
return UPGRADE_FAILURE;
}
--
2.52.0

View File

@ -1,300 +0,0 @@
From b5bee921b7f4cfbf7a2edbe55d4291f487f4a140 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 5 Feb 2026 12:17:06 +0100
Subject: [PATCH] Issue 7223 - Detect and log index ordering mismatch during
backend startup
Description:
Add `ldbm_instance_check_index_config()` function that checks on-disk
index data and logs a message in case of a mismatch with DSE config entry.
Relates: https://github.com/389ds/389-ds-base/issues/7223
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
---
ldap/servers/slapd/back-ldbm/instance.c | 262 ++++++++++++++++++++++++
1 file changed, 262 insertions(+)
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
index f9a546661..3fcdb5554 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -248,6 +248,266 @@ ldbm_instance_create_default_indexes(backend *be)
}
+/*
+ * Check if an index has integerOrderingMatch configured in DSE.
+ *
+ * This function performs an internal LDAP search to check if the index
+ * configuration entry has nsMatchingRule: integerOrderingMatch.
+ *
+ * Parameters:
+ * inst_name - backend instance name (e.g., "userRoot")
+ * index_name - name of the index to check (e.g., "parentid", "ancestorid")
+ *
+ * Returns:
+ * PR_TRUE if integerOrderingMatch is configured
+ * PR_FALSE if not configured or index entry doesn't exist
+ */
+static PRBool
+ldbm_instance_index_has_int_order_in_dse(const char *inst_name, const char *index_name)
+{
+ Slapi_PBlock *pb = NULL;
+ Slapi_Entry **entries = NULL;
+ char *idx_dn = NULL;
+ PRBool has_int_order = PR_FALSE;
+
+ idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,cn=ldbm database,cn=plugins,cn=config",
+ index_name, inst_name);
+ if (idx_dn == NULL) {
+ return PR_FALSE;
+ }
+
+ pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(pb, idx_dn, LDAP_SCOPE_BASE,
+ "(objectclass=nsIndex)", NULL, 0, NULL, NULL,
+ plugin_get_default_component_id(), 0);
+ slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+
+ if (entries && entries[0]) {
+ Slapi_Attr *mr_attr = NULL;
+ if (slapi_entry_attr_find(entries[0], "nsMatchingRule", &mr_attr) == 0) {
+ Slapi_Value *sval = NULL;
+ int idx;
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
+ idx != -1;
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
+ const struct berval *bval = slapi_value_get_berval(sval);
+ if (bval && bval->bv_val &&
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
+ has_int_order = PR_TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ slapi_ch_free_string(&idx_dn);
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return has_int_order;
+}
+
+/*
+ * Check a system index for ordering mismatch between config and on-disk data.
+ *
+ * This function compares what's configured in DSE (nsMatchingRule) with
+ * what's actually on disk. A mismatch can occur in two scenarios:
+ * 1. Ordering rule is configured but disk has lexicographic order
+ * (rule was added after index was created)
+ * 2. No ordering rule configured but disk has integer order
+ * (rule was removed after index was created with it)
+ *
+ * This function reads the first keys from the specified index and checks
+ * if they are stored in lexicographic order (string: "1" < "10" < "2") or
+ * integer order (numeric: "1" < "2" < "10").
+ *
+ * Parameters:
+ * be - backend
+ * index_name - name of the index to check (e.g., "parentid", "ancestorid")
+ *
+ */
+static void
+ldbm_instance_check_index_config(backend *be, const char *index_name)
+{
+ ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
+ struct attrinfo *ai = NULL;
+ dbi_db_t *db = NULL;
+ dbi_cursor_t dbc = {0};
+ dbi_val_t key = {0};
+ dbi_val_t data = {0};
+ int ret = 0;
+ PRBool config_has_int_order = PR_FALSE;
+ PRBool disk_has_int_order = PR_TRUE; /* Assume integer order until proven otherwise */
+ ID prev_id = 0;
+ int key_count = 0;
+ PRBool first_key = PR_TRUE;
+ PRBool found_ordering_evidence = PR_FALSE;
+
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
+ "Backend '%s': checking %s index ordering...\n",
+ inst->inst_name, index_name);
+
+ /* Check if integerOrderingMatch is configured in DSE */
+ config_has_int_order = ldbm_instance_index_has_int_order_in_dse(inst->inst_name, index_name);
+
+ /* Get attrinfo for the index */
+ ainfo_get(be, (char *)index_name, &ai);
+ if (ai == NULL || strcmp(ai->ai_type, index_name) != 0) {
+ /* No index config found */
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
+ "Backend '%s': no %s attrinfo found, skipping check\n",
+ inst->inst_name, index_name);
+ return;
+ }
+
+ /* Open the index file */
+ ret = dblayer_get_index_file(be, ai, &db, 0);
+ if (ret != 0 || db == NULL) {
+ /* Index file doesn't exist or can't be opened - this is fine for new instances */
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
+ "Backend '%s': could not open %s index file (ret=%d), skipping order check\n",
+ inst->inst_name, index_name, ret);
+ return;
+ }
+
+ /* Create a cursor to read keys */
+ ret = dblayer_new_cursor(be, db, NULL, &dbc);
+ if (ret != 0) {
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
+ "Backend '%s': could not create cursor on %s index (ret=%d)\n",
+ inst->inst_name, index_name, ret);
+ dblayer_release_index_file(be, ai, db);
+ return;
+ }
+
+ dblayer_value_init(be, &key);
+ dblayer_value_init(be, &data);
+
+ /*
+ * Read up to 100 unique keys and check their ordering.
+ * With lexicographic ordering: "1" < "10" < "100" < "2" < "20" < "3"
+ * With integer ordering: "1" < "2" < "3" < "10" < "20" < "100"
+ *
+ * If we find a case where prev_id > current_id (numerically), but the
+ * keys are still in order (lexicographically), then the index uses
+ * lexicographic ordering.
+ */
+ while (key_count < 100) {
+ ID current_id;
+
+ ret = dblayer_cursor_op(&dbc, first_key ? DBI_OP_MOVE_TO_FIRST : DBI_OP_NEXT_KEY, &key, &data);
+ first_key = PR_FALSE; /* Always advance cursor on next iteration */
+ if (ret != 0) {
+ break; /* No more keys or error */
+ }
+
+ /* Skip non-equality keys */
+ if (key.size < 2 || *(char *)key.data != EQ_PREFIX) {
+ continue;
+ }
+
+ /* Parse the ID from the key (format: "=<id>") */
+ current_id = (ID)strtoul((char *)key.data + 1, NULL, 10);
+ if (current_id == 0) {
+ continue; /* Invalid ID, skip */
+ }
+
+ key_count++;
+
+ if (prev_id != 0) {
+ /*
+ * Check ordering: if prev_id > current_id numerically,
+ * but we got this key after prev in DB order, then
+ * the index is using lexicographic ordering.
+ *
+ * Example: if we see "10" followed by "2", that's lexicographic
+ * because "10" < "2" as strings, but 10 > 2 as integers.
+ */
+ if (prev_id > current_id) {
+ /* Found evidence of lexicographic ordering */
+ disk_has_int_order = PR_FALSE;
+ found_ordering_evidence = PR_TRUE;
+ break;
+ } else if (prev_id < current_id) {
+ /*
+ * This is consistent with integer ordering, but we need
+ * to find a case that proves lexicographic ordering.
+ * For example, seeing "1" followed by "2" is ambiguous,
+ * but seeing "1" followed by "10" (not "2") proves lexicographic.
+ *
+ * A definitive test: if we see an ID followed by a smaller
+ * ID, that's lexicographic. If all IDs are strictly increasing,
+ * it could be either (or the index only has sequential IDs).
+ */
+ found_ordering_evidence = PR_TRUE;
+ }
+ }
+ prev_id = current_id;
+ }
+
+ /* Close the cursor and free values */
+ dblayer_cursor_op(&dbc, DBI_OP_CLOSE, NULL, NULL);
+ dblayer_value_free(be, &key);
+ dblayer_value_free(be, &data);
+
+ /* Release the index file */
+ dblayer_release_index_file(be, ai, db);
+
+ /*
+ * Report findings and check for config/disk mismatch.
+ * Log an error if there's a discrepancy between what's configured
+ * in DSE and what's actually on disk.
+ */
+ if (!found_ordering_evidence) {
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
+ "Backend '%s': %s index ordering check - "
+ "could not determine on-disk ordering (index may be empty or have sequential IDs only). "
+ "Config has integerOrderingMatch: %s\n",
+ inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
+ } else if (config_has_int_order && !disk_has_int_order) {
+ /* Config expects integer ordering, but disk has lexicographic - MISMATCH */
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
+ "Backend '%s': MISMATCH - %s index has integerOrderingMatch configured, "
+ "but on-disk data uses lexicographic ordering. "
+ "This will cause searches to return incorrect or incomplete results. "
+ "Please reindex the %s attribute: "
+ "dsconf <instance> backend index reindex --attr %s %s\n",
+ inst->inst_name, index_name, index_name, index_name, inst->inst_name);
+ } else if (!config_has_int_order && disk_has_int_order) {
+ /* Config expects lexicographic ordering, but disk has integer - MISMATCH */
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
+ "Backend '%s': MISMATCH - %s index does not have integerOrderingMatch configured, "
+ "but on-disk data uses integer ordering. "
+ "This will cause searches to return incorrect or incomplete results. "
+ "Please reindex the %s attribute: "
+ "dsconf <instance> backend index reindex --attr %s %s\n",
+ inst->inst_name, index_name, index_name, index_name, inst->inst_name);
+ } else {
+ /* Config and disk ordering match - no action needed */
+ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config",
+ "Backend '%s': %s index ordering check passed - "
+ "config has integerOrderingMatch: %s, on-disk data matches.\n",
+ inst->inst_name, index_name, config_has_int_order ? "yes" : "no");
+ }
+}
+
+/*
+ * Check system indexes for ordering mismatches.
+ * If a mismatch is detected, log an error advising the administrator
+ * to reindex the affected attribute.
+ *
+ * Note: We only check parentid here. The ancestorid index is a special
+ * system index that has no DSE config entry - its ordering is hardcoded
+ * in ldbm_instance_init_config_entry() and cannot be changed by users.
+ */
+static void
+ldbm_instance_check_indexes(backend *be)
+{
+ /* Check parentid index */
+ ldbm_instance_check_index_config(be, LDBM_PARENTID_STR);
+}
+
/* Starts a backend instance */
int
ldbm_instance_start(backend *be)
@@ -319,6 +579,8 @@ ldbm_instance_startall(struct ldbminfo *li)
ldbm_instance_register_modify_callback(inst);
vlv_init(inst);
slapi_mtn_be_started(inst->inst_be);
+ /* Check index configuration for potential issues */
+ ldbm_instance_check_indexes(inst->inst_be);
}
if (slapi_exist_referral(inst->inst_be)) {
slapi_be_set_flag(inst->inst_be, SLAPI_BE_FLAG_CONTAINS_REFERRAL);
--
2.52.0

File diff suppressed because it is too large Load Diff

View File

@ -1,135 +0,0 @@
From c1f0756b453b7ea219add236f60a72b3fc660af0 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 27 Jan 2026 14:26:29 +0100
Subject: [PATCH] Issue 7096 - (2nd) During replication online total init the
function idl_id_is_in_idlist is not scaling with large database (#7205)
Bug Description:
The fix for #7096 optimized the BDB backend's `idl_new_range_fetch()`
function to use ID ranges instead of checking the full ID list during
online total initialization. However, the LMDB backend's
`idl_lmdb_range_fetch()` function and its callback
`idl_range_add_id_cb()` were not updated and still use the non-scaling
`idl_id_is_in_idlist()` function.
Fix Description:
Apply the same optimization to the LMDB backend.
Fixes: https://github.com/389ds/389-ds-base/issues/7096
Reviewed by: @tbordaz, @droideck (Thanks!)
---
ldap/servers/slapd/back-ldbm/idl_new.c | 39 ++++++++++----------------
1 file changed, 15 insertions(+), 24 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
index 2d978353f..613d53815 100644
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
@@ -66,6 +66,7 @@ typedef struct {
size_t leftoverlen;
size_t leftovercnt;
IDList *idl;
+ IdRange_t *idrange_list;
int flag_err;
ID lastid;
ID suffix;
@@ -700,9 +701,9 @@ error:
}
}
}
- slapi_ch_free((void **)&leftover);
- idrange_free(&idrange_list);
}
+ slapi_ch_free((void **)&leftover);
+ idrange_free(&idrange_list);
slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
"Found %d candidates; error code is: %d\n",
idl ? idl->b_nids : 0, *flag_err);
@@ -716,7 +717,6 @@ static int
idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
{
idl_range_ctx_t *rctx = ctx;
- int idl_rc = 0;
ID id = 0;
if (key->data == NULL) {
@@ -779,10 +779,12 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
* found entry is the one from the suffix
*/
rctx->suffix = keyval;
- idl_rc = idl_append_extend(&rctx->idl, id);
- } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist(rctx->idl, keyval)) {
+ idl_append_extend(&rctx->idl, id);
+ idrange_add_id(&rctx->idrange_list, id);
+ } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist_ranges(rctx->idl, rctx->idrange_list, keyval)) {
/* the parent is the suffix or already in idl. */
- idl_rc = idl_append_extend(&rctx->idl, id);
+ idl_append_extend(&rctx->idl, id);
+ idrange_add_id(&rctx->idrange_list, id);
} else {
/* Otherwise, keep the {keyval,id} in leftover array */
if (!rctx->leftover) {
@@ -797,14 +799,7 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
rctx->leftovercnt++;
}
} else {
- idl_rc = idl_append_extend(&rctx->idl, id);
- }
- if (idl_rc) {
- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch",
- "Unable to extend id list (err=%d)\n", idl_rc);
- idl_free(&rctx->idl);
- rctx->flag_err = LDAP_UNWILLING_TO_PERFORM;
- return DBI_RC_NOTFOUND;
+ idl_append_extend(&rctx->idl, id);
}
#if defined(DB_ALLIDS_ON_READ)
/* enforce the allids read limit */
@@ -841,7 +836,6 @@ idl_lmdb_range_fetch(
{
int ret = 0;
int ret2 = 0;
- int idl_rc = 0;
dbi_cursor_t cursor = {0};
back_txn s_txn;
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
@@ -891,6 +885,7 @@ idl_lmdb_range_fetch(
idl_range_ctx.lastid = 0;
idl_range_ctx.count = 0;
idl_range_ctx.index_id = index_id;
+ idl_range_ctx.idrange_list = NULL;
if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
struct _back_info_index_key bck_info;
/* We are doing a bulk import
@@ -966,22 +961,18 @@ error:
while(remaining > 0) {
for (size_t i = 0; i < idl_range_ctx.leftovercnt; i++) {
if (idl_range_ctx.leftover[i].key > 0 &&
- idl_id_is_in_idlist(idl_range_ctx.idl, idl_range_ctx.leftover[i].key) != 0) {
+ idl_id_is_in_idlist_ranges(idl_range_ctx.idl, idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].key) != 0) {
/* if the leftover key has its parent in the idl */
- idl_rc = idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
- if (idl_rc) {
- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch",
- "Unable to extend id list (err=%d)\n", idl_rc);
- idl_free(&idl_range_ctx.idl);
- break;
- }
+ idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
+ idrange_add_id(&idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].id);
idl_range_ctx.leftover[i].key = 0;
remaining--;
}
}
}
- slapi_ch_free((void **)&idl_range_ctx.leftover);
}
+ slapi_ch_free((void **)&idl_range_ctx.leftover);
+ idrange_free(&idl_range_ctx.idrange_list);
*flag_err = idl_range_ctx.flag_err;
slapi_log_err(SLAPI_LOG_FILTER, "idl_lmdb_range_fetch",
"Found %d candidates; error code is: %d\n",
--
2.52.0

View File

@ -1,877 +0,0 @@
From 35237d57daf6fdf20a42c3cef27130ba70f0cdc2 Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Tue, 18 Nov 2025 21:57:10 +0530
Subject: [PATCH] Issue 7076, 6992, 6784, 6214 - Fix CI test failures (#7077)
- Fixed import test bugs in regression_test.py (cleanup handler, LDIF permissions) -
https://github.com/389ds/389-ds-base/issues/6992
- Fixed ModRDN cache corruption on failed operations (parent update check, cache cleanup)
- Fixed attribute uniqueness test fixture cleanup in attruniq_test.py
- mproved test stability by fixing race conditions in replication, healthcheck,
web UI, memberOf, and basic tests.
- Fixed entrycache_eviction_test.py to track incremental log counts instead of cumulative -
https://github.com/389ds/389-ds-base/issues/6784
Fixes: https://github.com/389ds/389-ds-base/issues/7076
Relates: https://github.com/389ds/389-ds-base/issues/6992
Relates: https://github.com/389ds/389-ds-base/issues/6784
Fixes: https://github.com/389ds/389-ds-base/issues/6214
Reviewed by: @vashirov, @progier389 (Thanks!)
---
dirsrvtests/tests/suites/basic/basic_test.py | 4 +-
.../healthcheck/health_system_indexes_test.py | 3 +
.../tests/suites/import/regression_test.py | 388 +++++++++++++++++-
.../suites/memberof_plugin/fixup_test.py | 8 +-
.../tests/suites/plugins/attruniq_test.py | 84 ++--
.../suites/replication/regression_m2_test.py | 4 +
.../replication/repl_log_monitoring_test.py | 8 +-
.../suites/webui/database/database_test.py | 1 +
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 44 +-
9 files changed, 481 insertions(+), 63 deletions(-)
diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
index be825efe9..e9b611439 100644
--- a/dirsrvtests/tests/suites/basic/basic_test.py
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
@@ -593,7 +593,7 @@ def test_basic_import_export(topology_st, import_example_ldif):
#
# Test online/offline LDIF imports
#
- topology_st.standalone.start()
+ topology_st.standalone.restart()
# topology_st.standalone.config.set('nsslapd-errorlog-level', '1')
# Generate a test ldif (50k entries)
@@ -691,6 +691,8 @@ def test_basic_backup(topology_st, import_example_ldif):
log.info('Running test_basic_backup...')
+ topology_st.standalone.restart()
+
backup_dir = topology_st.standalone.get_bak_dir() + '/backup_test_online'
log.info(f'Backup directory is {backup_dir}')
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index 571465562..4cd4d0c70 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -172,6 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
log.info("Re-add the parentId index")
backend = Backends(standalone).get("userRoot")
backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
+ standalone.restart()
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
@@ -215,6 +216,7 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
log.info("Re-add the integerOrderingMatch matching rule")
parentid_index = Index(standalone, PARENTID_DN)
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
+ standalone.restart()
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
@@ -445,6 +447,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
backend = Backends(standalone).get("userRoot")
backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
backend.add_index("nsuniqueid", ["eq"])
+ standalone.restart()
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py
index 18611de35..bdbb516e8 100644
--- a/dirsrvtests/tests/suites/import/regression_test.py
+++ b/dirsrvtests/tests/suites/import/regression_test.py
@@ -5,6 +5,7 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
#
+from abc import ABC, abstractmethod
from decimal import *
import ldap
import logging
@@ -16,9 +17,9 @@ from lib389.backend import Backends
from lib389.properties import TASK_WAIT
from lib389.topologies import topology_st as topo
from lib389.dbgen import dbgen_users
-from lib389._constants import DEFAULT_SUFFIX
+from lib389._constants import DEFAULT_SUFFIX, DEFAULT_BENAME
from lib389.tasks import *
-from lib389.idm.user import UserAccounts
+from lib389.idm.user import UserAccounts, UserAccount
from lib389.idm.directorymanager import DirectoryManager
from lib389.dbgen import *
from lib389.utils import *
@@ -92,7 +93,203 @@ class AddDelUsers(threading.Thread):
return self._ran
-def test_replay_import_operation(topo):
+def get_backend_by_name(inst, bename):
+ bes = Backends(inst)
+ bename = bename.lower()
+ be = [ be for be in bes if be.get_attr_val_utf8_l('cn') == bename ]
+ return be[0] if len(be) == 1 else None
+
+
+class LogHandler():
+ def __init__(self, logfd, patterns):
+ self.logfd = logfd
+ self.patterns = [ p.lower() for p in patterns ]
+ self.pos = logfd.tell()
+ self.last_result = None
+
+ def zero(self):
+ return [0 for _ in range(len(self.patterns))]
+
+ def countCaptures(self):
+ res = self.zero()
+ self.logfd.seek(self.pos)
+ for line in iter(self.logfd.readline, ''):
+ # Ignore autotune messages that may confuse the counts
+ if 'bdb_start_autotune' in line:
+ continue
+ # Ignore LMDB size warnings that may confuse the counts
+ if 'dbmdb_ctx_t_db_max_size_set' in line:
+ continue
+ log.info(f'ERROR LOG line is {line.strip()}')
+ for idx,pattern in enumerate(self.patterns):
+ if pattern in line.lower():
+ res[idx] += 1
+ self.pos = self.logfd.tell()
+ self.last_result = res
+ log.info(f'ERROR LOG counts are: {res}')
+ return res
+
+ def seek2end(self):
+ self.pos = os.fstat(self.logfd.fileno()).st_size
+
+ def check(self, idx, val):
+ count = self.last_result[idx]
+ assert count == val , f"Should have {val} '{self.patterns[idx]}' messages but got: {count} - idx = {idx}"
+
+
+class IEHandler(ABC):
+ def __init__(self, inst, errlog, ldifname, bename=DEFAULT_BENAME, suffix=None):
+ self.inst = inst
+ self.errlog = errlog
+ self.ldifname = ldifname
+ self.bename = bename
+ self.suffix = suffix
+ self.ldif = ldifname if ldifname.startswith('/') else f'{inst.get_ldif_dir()}/{ldifname}.ldif'
+
+ @abstractmethod
+ def get_name(self):
+ pass
+
+ @abstractmethod
+ def _run_task_b(self):
+ pass
+
+ @abstractmethod
+ def _run_task_s(self):
+ pass
+
+ @abstractmethod
+ def _run_offline(self):
+ pass
+
+ @abstractmethod
+ def _set_log_pattern(self, success):
+ pass
+
+ def run(self, extra_checks, success=True):
+ if self.errlog:
+ self._set_log_pattern(success)
+ self.errlog.seek2end()
+
+ if self.inst.status():
+ if self.bename:
+ log.info(f"Performing online {self.get_name()} of backend {self.bename} into LDIF file {self.ldif}")
+ r = self._run_task_b()
+ else:
+ log.info(f"Performing online {self.get_name()} of suffix {self.suffix} into LDIF file {self.ldif}")
+ r = self._run_task_s()
+ r.wait()
+ time.sleep(1)
+ else:
+ if self.bename:
+ log.info(f"Performing offline {self.get_name()} of backend {self.bename} into LDIF file {self.ldif}")
+ else:
+ log.info(f"Performing offline {self.get_name()} of suffix {self.suffix} into LDIF file {self.ldif}")
+ self._run_offline()
+ if self.errlog:
+ expected_counts = ['*' for _ in range(len(self.errlog.patterns))]
+ for (idx, val) in extra_checks:
+ expected_counts[idx] = val
+ res = self.errlog.countCaptures()
+ log.info(f'Expected errorlog counts are: {expected_counts}')
+ if success is True or success is False:
+ log.info(f'Number of {self.errlog.patterns[0]} in errorlog is: {res[0]}')
+ assert res[0] >= 1
+ for (idx, val) in extra_checks:
+ self.errlog.check(idx, val)
+
+ def check_db(self):
+ assert self.inst.dbscan(bename=self.bename, index='id2entry')
+
+
+class Importer(IEHandler):
+ def get_name(self):
+ return "import"
+
+ def _set_log_pattern(self, success):
+ if success is True:
+ self.errlog.patterns[0] = 'import complete'
+ elif success is False:
+ self.errlog.patterns[0] = 'import failed'
+
+ def _run_task_b(self):
+ bes = Backends(self.inst)
+ r = bes.import_ldif(self.bename, [self.ldif,], include_suffixes=self.suffix)
+ return r
+
+ def _run_task_s(self):
+ r = ImportTask(self.inst)
+ r.import_suffix_from_ldif(self.ldif, self.suffix)
+ return r
+
+ def _run_offline(self):
+ log.info(f'self.inst.ldif2db({self.bename}, {self.suffix}, ...)')
+ if self.suffix is None:
+ self.inst.ldif2db(self.bename, self.suffix, None, False, self.ldif)
+ else:
+ self.inst.ldif2db(self.bename, [self.suffix, ], None, False, self.ldif)
+
+
+class Exporter(IEHandler):
+ def get_name(self):
+ return "export"
+
+ def _set_log_pattern(self, success):
+ if success is True:
+ self.errlog.patterns[0] = 'export finished'
+ elif success is False:
+ self.errlog.patterns[0] = 'export failed'
+
+ def _run_task_b(self):
+ bes = Backends(self.inst)
+ r = bes.export_ldif(self.bename, self.ldif, include_suffixes=self.suffix)
+ return r
+
+ def _run_task_s(self):
+ r = ExportTask(self.inst)
+ r.export_suffix_to_ldif(self.ldif, self.suffix)
+ return r
+
+ def _run_offline(self):
+ self.inst.db2ldif(self.bename, self.suffix, None, False, False, self.ldif)
+
+
+def preserve_func(topo, request, restart):
+ # Ensure that topology get preserved helper
+ inst = topo.standalone
+
+ def fin():
+ if restart:
+ inst.restart()
+ Importer(inst, None, "save").run(())
+
+ r = Exporter(inst, None, "save")
+ if not os.path.isfile(r.ldif):
+ r.run(())
+ request.addfinalizer(fin)
+
+
+@pytest.fixture(scope="function")
+def preserve(topo, request):
+ # Ensure that topology get preserved (no restart)
+ preserve_func(topo, request, False)
+
+
+@pytest.fixture(scope="function")
+def preserve_r(topo, request):
+ # Ensure that topology get preserved (with restart)
+ preserve_func(topo, request, True)
+
+
+@pytest.fixture(scope="function")
+def verify(topo):
+ # Check that backend is not broken
+ inst = topo.standalone
+ dn=f'uid=demo_user,ou=people,{DEFAULT_SUFFIX}'
+ assert UserAccount(inst,dn).exists()
+
+
+def test_replay_import_operation(topo, preserve_r, verify):
""" Check after certain failed import operation, is it
possible to replay an import operation
@@ -487,7 +684,190 @@ def test_ldif2db_after_backend_create(topo):
import_time_2 = create_backend_and_import(instance, ldif_file_2, 'o=test_2', 'test_2')
log.info('Import times should be approximately the same')
- assert abs(import_time_1 - import_time_2) < 5
+ assert abs(import_time_1 - import_time_2) < 15
+
+
+def test_ldif_missing_suffix_entry(topo, request, verify):
+ """Test that ldif2db/import aborts if suffix entry is not in the ldif
+
+ :id: 731bd0d6-8cc8-11f0-8ef2-c85309d5c3e3
+ :setup: Standalone Instance
+ :steps:
+ 1. Prepare final cleanup
+ 2. Add a few users
+ 3. Export ou=people subtree
+ 4. Online import using backend name ou=people subtree
+ 5. Online import using suffix name ou=people subtree
+ 6. Stop the instance
+ 7. Offline import using backend name ou=people subtree
+ 8. Offline import using suffix name ou=people subtree
+ 9. Generate ldif with a far away suffix
+ 10. Offline import using backend name and "far" ldif
+ 11. Offline import using suffix name and "far" ldif
+ 12. Start the instance
+ 13. Online import using backend name and "far" ldif
+ 14. Online import using suffix name and "far" ldif
+ :expectedresults:
+ 1. Operation successful
+ 2. Operation successful
+ 3. Operation successful
+ 4. Import should success, skip all entries, db should exists
+ 5. Import should success, skip all entries, db should exists
+ 6. Operation successful
+ 7. Import should success, skip all entries, db should exists
+ 8. Import should success, skip all entries, db should exists
+ 9. Operation successful
+ 10. Import should success, skip all entries, db should exists
+ 11. Import should success, 10 entries skipped, db should exists
+ 12. Operation successful
+ 13. Import should success, skip all entries, db should exists
+ 14. Import should success, 10 entries skipped, db should exists
+ """
+
+ inst = topo.standalone
+ inst.config.set('nsslapd-errorlog-level', '266354688')
+ no_suffix_on = (
+ (1, 0), # no errors are expected.
+ (2, 1), # 1 warning is expected.
+ (3, 0), # no 'no parent' warning is expected.
+ (4, 1), # 1 'all entries were skipped' warning
+ (5, 0), # no 'returning task warning' info message
+ )
+ no_suffix_off = (
+ (1, 0), # no errors are expected.
+ (2, 1), # 1 warning is expected.
+ (3, 0), # no 'no parent' warning is expected.
+ (4, 1), # 1 'all entries were skipped' warning
+ (5, 1), # 1 'returning task warning' info message
+ )
+
+ far_suffix_on = (
+ (1, 0), # no errors are expected.
+ (2, 1), # 1 warning (consolidated, pre-check aborts after 4 entries)
+ (3, 0), # 0 'no parent' warnings (pre-check aborts before processing)
+ (4, 1), # 1 'all entries were skipped' warning (from pre-check)
+ (5, 0), # 0 'returning task warning' info message (online import)
+ )
+ # Backend-specific behavior for orphan detection when suffix parameter is provided
+ nbw = 0 if get_default_db_lib() == "bdb" else 10
+ far_suffix_with_suffix_on = (
+ (1, 0), # no errors are expected.
+ (2, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) warnings
+ (3, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) 'no parent' warnings
+ (4, 0), # 0 'all entries were skipped' warning (no pre-check abort)
+ (5, 0), # 0 'returning task warning' info message (online import)
+ )
+ far_suffix_off = (
+ (1, 0), # no errors are expected.
+ (2, 1), # 1 warning (consolidated, pre-check detects missing suffix)
+ (3, 0), # 0 'no parent' warnings (pre-check aborts before processing)
+ (4, 1), # 1 'all entries were skipped' warning (from pre-check)
+ (5, 1), # 1 'returning task warning' info message (offline import)
+ )
+ far_suffix_with_suffix_off = (
+ (1, 0), # no errors are expected.
+ (2, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) warnings
+ (3, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) 'no parent' warnings
+ (4, 0), # 0 'all entries were skipped' warning (no pre-check abort)
+ (5, 0), # 0 'returning task warning' (rc=0, successful import of suffix)
+ )
+
+ with open(inst.ds_paths.error_log, 'at+') as fd:
+ patterns = (
+ "Reserved for IEHandler",
+ " ERR ",
+ " WARN ",
+ "has no parent",
+ "all entries were skipped",
+ "returning task warning",
+ )
+ errlog = LogHandler(fd, patterns)
+ no_errors = ((1, 0), (2, 0)) # no errors nor warnings are expected.
+
+
+ # 1. Prepare final cleanup
+ Exporter(inst, errlog, "full").run(no_errors)
+
+ def fin():
+ inst.start()
+ with open(inst.ds_paths.error_log, 'at+') as cleanup_fd:
+ cleanup_errlog = LogHandler(cleanup_fd, patterns)
+ Importer(inst, cleanup_errlog, "full").run(no_errors)
+
+ if not DEBUGGING:
+ request.addfinalizer(fin)
+
+ # 2. Add a few users
+ user = UserAccounts(inst, DEFAULT_SUFFIX)
+ users = [ user.create_test_user(uid=i) for i in range(10) ]
+
+ # 3. Export ou=people subtree
+ e = Exporter(inst, errlog, "people", suffix=f'ou=people,{DEFAULT_SUFFIX}')
+ e.run(no_errors) # no errors nor warnings are expected.
+
+ # 4. Online import using backend name ou=people subtree
+ e = Importer(inst, errlog, "people")
+ e.run(no_suffix_on)
+ e.check_db()
+
+ # 5. Online import using suffix name ou=people subtree
+ e = Importer(inst, errlog, "people", suffix=DEFAULT_SUFFIX)
+ e.run(no_suffix_on)
+ e.check_db()
+
+ # 6. Stop the instance
+ inst.stop()
+
+ # 7. Offline import using backend name ou=people subtree
+ e = Importer(inst, errlog, "people")
+ e.run(no_suffix_off)
+ e.check_db()
+
+ # 8. Offline import using suffix name ou=people subtree
+ e = Importer(inst, errlog, "people", suffix=DEFAULT_SUFFIX)
+ e.run(no_suffix_off)
+ e.check_db()
+
+ # 9. Generate ldif with a far away suffix
+ e = Importer(inst, errlog, "full")
+ people_ldif = e.ldif
+ e = Importer(inst, errlog, "far")
+ with open(e.ldif, "wt") as fout:
+ with open(people_ldif, "rt") as fin:
+ # Copy version
+ line = fin.readline()
+ fout.write(line)
+ line = fin.readline()
+ fout.write(line)
+ # Generate fake entries
+ for idx in range(10):
+ fout.write(f"dn: uid=id{idx},dc=foo\nobjectclasses: extensibleObject\n\n")
+ for line in iter(fin.readline, ''):
+ fout.write(line)
+
+ os.chmod(e.ldif, 0o644)
+
+ # 10. Offline import using backend name ou=people subtree
+ e.run(far_suffix_off)
+ e.check_db()
+
+ # 11. Offline import using suffix name ou=people subtree
+ e = Importer(inst, errlog, "far", suffix=DEFAULT_SUFFIX)
+ e.run(far_suffix_with_suffix_off)
+ e.check_db()
+
+ # 12. Start the instance
+ inst.start()
+
+ # 13. Online import using backend name ou=people subtree
+ e = Importer(inst, errlog, "far")
+ e.run(far_suffix_on)
+ e.check_db()
+
+ # 14. Online import using suffix name ou=people subtree
+ e = Importer(inst, errlog, "far", suffix=DEFAULT_SUFFIX)
+ e.run(far_suffix_with_suffix_on)
+ e.check_db()
if __name__ == '__main__':
diff --git a/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py b/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py
index 5aac40d2b..44804bd1c 100644
--- a/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py
+++ b/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py
@@ -44,7 +44,10 @@ def test_fixup_task_limit(topo):
group = groups.create(properties={'cn': 'test'})
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
- for idx in range(400):
+ # Turn on access log buffering to speed up user creation
+ buffering = topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logbuffering')
+ topo.standalone.config.set('nsslapd-accesslog-logbuffering', 'on')
+ for idx in range(6000):
user = users.create(properties={
'uid': 'testuser%s' % idx,
'cn' : 'testuser%s' % idx,
@@ -55,6 +58,9 @@ def test_fixup_task_limit(topo):
})
group.add('member', user.dn)
+ # Restore access log buffering
+ topo.standalone.config.set('nsslapd-accesslog-logbuffering', buffering)
+
# Configure memberOf plugin
memberof = MemberOfPlugin(topo.standalone)
memberof.enable()
diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py
index 6eaee08a4..a2be413c8 100644
--- a/dirsrvtests/tests/suites/plugins/attruniq_test.py
+++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py
@@ -84,14 +84,23 @@ def containers(topology_st, request):
def attruniq(topology_st, request):
log.info('Setup attribute uniqueness plugin')
attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
- attruniq.create(properties={'cn': 'attruniq'})
- attruniq.add_unique_attribute('cn')
+
+ if attruniq.exists():
+ attruniq.delete()
+ topology_st.standalone.restart()
+
+ attruniq.create(properties={
+ 'cn': 'attruniq',
+ 'uniqueness-attribute-name': 'cn',
+ 'uniqueness-subtrees': 'cn=config',
+ 'nsslapd-pluginEnabled': 'on'
+ })
topology_st.standalone.restart()
def fin():
if attruniq.exists():
- attruniq.disable()
attruniq.delete()
+ topology_st.standalone.restart()
request.addfinalizer(fin)
@@ -250,8 +259,8 @@ def test_modrdn_attr_uniqueness(topology_st, attruniq):
group1 = groups.create(properties={'cn': 'group1'})
group2 = groups.create(properties={'cn': 'group2'})
- attruniq.add_unique_attribute('mail')
- attruniq.add_unique_subtree(group2.dn)
+ attruniq.replace('uniqueness-attribute-name', 'mail')
+ attruniq.replace('uniqueness-subtrees', group2.dn)
attruniq.enable_all_subtrees()
log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
attruniq.enable()
@@ -274,9 +283,6 @@ def test_modrdn_attr_uniqueness(topology_st, attruniq):
assert 'attribute value already exist' in str(excinfo.value)
log.debug(excinfo.value)
- log.debug('Move user2 to group1')
- user2.rename(f'uid={user2.rdn}', group1.dn)
-
user1.delete()
user2.delete()
@@ -302,17 +308,11 @@ def test_multiple_attr_uniqueness(topology_st, attruniq):
6. Should raise CONSTRAINT_VIOLATION
"""
- try:
- attruniq.add_unique_attribute('mail')
- attruniq.add_unique_attribute('mailAlternateAddress')
- attruniq.add_unique_subtree(DEFAULT_SUFFIX)
- attruniq.enable_all_subtrees()
- log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
- attruniq.enable()
- except ldap.LDAPError as e:
- log.fatal('test_multiple_attribute_uniqueness: Failed to configure plugin for "mail": error {}'.format(e.args[0]['desc']))
- assert False
-
+ attruniq.replace('uniqueness-attribute-name', ['mail', 'mailAlternateAddress'])
+ attruniq.replace('uniqueness-subtrees', DEFAULT_SUFFIX)
+ attruniq.enable_all_subtrees()
+ log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
+ attruniq.enable()
topology_st.standalone.restart()
users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
@@ -383,8 +383,9 @@ def test_exclude_subtrees(topology_st, attruniq):
16. Success
17. Success
"""
- attruniq.add_unique_attribute('telephonenumber')
- attruniq.add_unique_subtree(DEFAULT_SUFFIX)
+ # Replace dummy config with actual test config
+ attruniq.replace('uniqueness-attribute-name', 'telephonenumber')
+ attruniq.replace('uniqueness-subtrees', DEFAULT_SUFFIX)
attruniq.enable_all_subtrees()
attruniq.enable()
topology_st.standalone.restart()
@@ -517,10 +518,18 @@ def test_matchingrule_attr(topology_st):
"""
inst = topology_st.standalone
-
attruniq = AttributeUniquenessPlugin(inst,
dn="cn=attribute uniqueness,cn=plugins,cn=config")
- attruniq.add_unique_attribute('cn:CaseExactMatch:')
+
+ if attruniq.exists():
+ attruniq.delete()
+ inst.restart()
+
+ attruniq.create(properties={
+ 'cn': 'attribute uniqueness',
+ 'uniqueness-attribute-name': 'cn:CaseExactMatch:',
+ 'uniqueness-subtrees': DEFAULT_SUFFIX
+ })
attruniq.enable_all_subtrees()
attruniq.enable()
inst.restart()
@@ -595,7 +604,7 @@ def test_one_container_add(topology_st, attruniq, containers, active_user_1):
active_2.delete()
log.info('Setup attribute uniqueness plugin for "cn" attribute')
- attruniq.add_unique_subtree(ACTIVE_DN)
+ attruniq.replace('uniqueness-subtrees', ACTIVE_DN)
attruniq.enable()
topology_st.standalone.restart()
@@ -628,7 +637,7 @@ def test_one_container_mod(topology_st, attruniq, containers,
"""
log.info('Setup attribute uniqueness plugin for "cn" attribute')
- attruniq.add_unique_subtree(ACTIVE_DN)
+ attruniq.replace('uniqueness-subtrees', ACTIVE_DN)
attruniq.enable()
topology_st.standalone.restart()
@@ -656,7 +665,7 @@ def test_one_container_modrdn(topology_st, attruniq, containers,
"""
log.info('Setup attribute uniqueness plugin for "cn" attribute')
- attruniq.add_unique_subtree(ACTIVE_DN)
+ attruniq.replace('uniqueness-subtrees', ACTIVE_DN)
attruniq.enable()
topology_st.standalone.restart()
@@ -690,8 +699,7 @@ def test_multiple_containers_add(topology_st, attruniq, containers,
"""
log.info('Setup attribute uniqueness plugin for "cn" attribute')
- attruniq.add_unique_subtree(ACTIVE_DN)
- attruniq.add_unique_subtree(STAGE_DN)
+ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN])
attruniq.enable()
topology_st.standalone.restart()
@@ -789,8 +797,7 @@ def test_multiple_containers_mod(topology_st, attruniq, containers,
"""
log.info('Setup attribute uniqueness plugin for "cn" attribute')
- attruniq.add_unique_subtree(ACTIVE_DN)
- attruniq.add_unique_subtree(STAGE_DN)
+ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN])
attruniq.enable()
topology_st.standalone.restart()
@@ -874,8 +881,8 @@ def test_multiple_containers_modrdn(topology_st, attruniq, containers,
"""
log.info('Setup attribute uniqueness plugin for "cn" attribute')
- attruniq.add_unique_subtree(ACTIVE_DN)
- attruniq.add_unique_subtree(STAGE_DN)
+ # Replace dummy subtree with actual test subtrees
+ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN])
attruniq.enable()
topology_st.standalone.restart()
@@ -993,7 +1000,10 @@ def test_invalid_config_missing_attr_name(topology_st):
_config_file(topology_st, action='save')
attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
- attruniq.create(properties={'cn': 'attruniq'})
+ attruniq.create(properties={
+ 'cn': 'attruniq',
+ 'uniqueness-subtrees': DEFAULT_SUFFIX
+ })
attruniq.enable()
topology_st.standalone.errorlog_file = open(topology_st.standalone.errlog, "r")
@@ -1040,9 +1050,11 @@ def test_invalid_config_invalid_subtree(topology_st):
_config_file(topology_st, action='save')
attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
- attruniq.create(properties={'cn': 'attruniq'})
- attruniq.add_unique_attribute('cn')
- attruniq.add_unique_subtree('invalid_subtree')
+ attruniq.create(properties={
+ 'cn': 'attruniq',
+ 'uniqueness-attribute-name': 'cn',
+ 'uniqueness-subtrees': 'invalid_subtree'
+ })
attruniq.enable()
topology_st.standalone.errorlog_file = open(topology_st.standalone.errlog, "r")
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
index ba1ffcc9c..db5140b0b 100644
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
@@ -1221,6 +1221,10 @@ def test_rid_starting_with_0(topo_m2, request):
for replica,rid in zip(replicas, ['010', '020']):
replica.replace('nsDS5ReplicaId', rid)
+ # Restart required - replica IDs are loaded at startup and cached in memory
+ S1.restart()
+ S2.restart()
+
# Restore replica id in finalizer
def fin():
for replica,rid in zip(replicas, ['1', '2']):
diff --git a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
index 665fcb96f..b2b2d25a2 100644
--- a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
+++ b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
@@ -369,11 +369,13 @@ def test_replication_log_monitoring_multi_suffix(topo_m4):
repl.ensure_agreement(s1, s2)
repl.ensure_agreement(s2, s1)
- # Allow initial topology to settle before capturing metrics
+ # Wait for all the setup replication to settle, then clear the logs
for suffix in all_suffixes:
repl = ReplicationManager(suffix)
- repl.test_replication_topology(topo_m4)
-
+ for s1 in suppliers:
+ for s2 in suppliers:
+ if s1 != s2:
+ repl.wait_for_replication(s1, s2)
for supplier in suppliers:
supplier.deleteAccessLogs(restart=True)
diff --git a/dirsrvtests/tests/suites/webui/database/database_test.py b/dirsrvtests/tests/suites/webui/database/database_test.py
index ef105d262..05ddb6b00 100644
--- a/dirsrvtests/tests/suites/webui/database/database_test.py
+++ b/dirsrvtests/tests/suites/webui/database/database_test.py
@@ -138,6 +138,7 @@ def test_chaining_configuration_availability(topology_st, page, browser_name):
log.info('Click on Chaining Configuration and check if element is loaded.')
frame.get_by_role('tab', name='Database', exact=True).click()
frame.locator('#chaining-config').click()
+ frame.locator('#chaining-page').wait_for()
frame.locator('#defSizeLimit').wait_for()
assert frame.locator('#defSizeLimit').is_visible()
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index e3b7e5783..b1a29ff7f 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -1080,7 +1080,9 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
}
}
}
- if (slapi_sdn_get_dn(dn_newsuperiordn) != NULL) {
+ /* Only update parent if we're actually moving to a NEW parent (not the same parent) */
+ if (slapi_sdn_get_dn(dn_newsuperiordn) != NULL &&
+ slapi_sdn_compare(dn_newsuperiordn, &dn_parentdn) != 0) {
/* Push out the db modifications from the parent entry */
retval = modify_update_all(be, pb, &parent_modify_context, &txn);
if (DBI_RC_RETRY == retval) {
@@ -1335,11 +1337,6 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
goto common_return;
error_return:
- /* Revert the caches if this is the parent operation */
- if (parent_op && betxn_callback_fails) {
- revert_cache(inst, &parent_time);
- }
-
/* result already sent above - just free stuff */
if (postentry) {
slapi_entry_free(postentry);
@@ -1417,13 +1414,6 @@ error_return:
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval);
}
slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message);
-
- /* As it is a BETXN plugin failure then
- * revert the caches if this is the parent operation
- */
- if (parent_op) {
- revert_cache(inst, &parent_time);
- }
}
retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN);
@@ -1437,6 +1427,15 @@ error_return:
}
}
+ /* Revert the caches if this is the parent operation and cache modifications were made.
+ * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed,
+ * so we should only revert if we got past that point (i.e., BETXN POST plugin failures).
+ * For BETXN PRE failures, no cache modifications were made to parent/newparent entries.
+ */
+ if (parent_op && betxn_callback_fails && postentry) {
+ revert_cache(inst, &parent_time);
+ }
+
common_return:
/* result code could be used in the bepost plugin functions. */
@@ -1482,12 +1481,22 @@ common_return:
"operation failed, the target entry is cleared from dncache (%s)\n", slapi_entry_get_dn(ec->ep_entry));
CACHE_REMOVE(&inst->inst_dncache, bdn);
CACHE_RETURN(&inst->inst_dncache, &bdn);
+
+ /* Also remove ec from entry cache and free it since the operation failed */
+ if (inst && cache_is_in_cache(&inst->inst_cache, ec)) {
+ CACHE_REMOVE(&inst->inst_cache, ec);
+ CACHE_RETURN(&inst->inst_cache, &ec);
+ } else {
+ /* ec was not in cache, just free it */
+ backentry_free(&ec);
+ }
+ ec = NULL;
}
if (ec && inst) {
CACHE_RETURN(&inst->inst_cache, &ec);
+ ec = NULL;
}
- ec = NULL;
}
if (inst) {
@@ -1817,6 +1826,8 @@ moddn_get_newdn(Slapi_PBlock *pb, Slapi_DN *dn_olddn, Slapi_DN *dn_newrdn, Slapi
/*
* Return the entries to the cache.
+ * For the original entry 'e', we should NOT remove it from cache on failure,
+ * as it's still a valid entry in the directory.
*/
static void
moddn_unlock_and_return_entry(
@@ -1825,12 +1836,9 @@ moddn_unlock_and_return_entry(
{
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
- /* Something bad happened so we should give back all the entries */
+ /* Unlock and return the entry to the cache */
if (*targetentry != NULL) {
cache_unlock_entry(&inst->inst_cache, *targetentry);
- if (cache_is_in_cache(&inst->inst_cache, *targetentry)) {
- CACHE_REMOVE(&inst->inst_cache, *targetentry);
- }
CACHE_RETURN(&inst->inst_cache, targetentry);
*targetentry = NULL;
}
--
2.52.0

View File

@ -1,57 +0,0 @@
From 04a0f5560ea741972b9c264af23b377893ab7133 Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Thu, 5 Feb 2026 15:41:09 +0530
Subject: [PATCH] Issue 7076 - Fix revert_cache() never called in modrdn
(#7220)
Description: The postentry check in PR #7077 was broken - postentry is always NULL
at that point, fixed by removing the check.
Relates: #7076
Reviewed by: @vashirov, @mreynolds389, @droideck (Thanks!)
---
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index b1a29ff7f..36377fc01 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -103,6 +103,7 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
Connection *pb_conn = NULL;
int32_t parent_op = 0;
int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */
+ int32_t cache_mod_phase = 0; /* set when we reach the cache modification phase */
struct timespec parent_time;
Slapi_Mods *smods_add_rdn = NULL;
@@ -1229,6 +1230,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
goto error_return;
}
+ /* We're now past the BETXN PRE phase and entering the cache modification phase */
+ cache_mod_phase = 1;
postentry = slapi_entry_dup(ec->ep_entry);
if (parententry != NULL) {
@@ -1427,12 +1430,11 @@ error_return:
}
}
- /* Revert the caches if this is the parent operation and cache modifications were made.
- * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed,
- * so we should only revert if we got past that point (i.e., BETXN POST plugin failures).
- * For BETXN PRE failures, no cache modifications were made to parent/newparent entries.
+ /* Revert the caches if this is the parent operation AND we reached the
+ * cache modification phase. If BETXN PRE fails, cache_mod_phase is 0
+ * and we don't need to revert since no cache modifications were made.
*/
- if (parent_op && betxn_callback_fails && postentry) {
+ if (parent_op && betxn_callback_fails && cache_mod_phase) {
revert_cache(inst, &parent_time);
}
--
2.52.0

View File

@ -1,24 +0,0 @@
From fb8acf6141b069e250be3821c1e70ee7ed448720 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Wed, 11 Feb 2026 19:53:44 +0100
Subject: [PATCH] Issue 6947 - Fix health_system_indexes_test.py
---
.../tests/suites/healthcheck/health_system_indexes_test.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index 4cd4d0c70..3b3651b38 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -99,6 +99,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searc
"memberof",
]
args.dry_run = False
+ args.exclude_check = []
# If we are using BDB as a backend, we will get error DSBLE0006 on new versions
if (
--
2.52.0

View File

@ -1,71 +0,0 @@
From 8761b28fd2f2cf3d2e2119bdf1b348bc7d2d1834 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 9 Feb 2026 13:18:09 +0100
Subject: [PATCH] Issue 7121 - (2nd) LeakSanitizer: various leaks during
replication (#7212)
Bug Description:
With the previous fix 75e0e487545893a7b0d83f94f9264c10f8bb0353 applied,
server can crash in ber_bvcpy.
```
Program terminated with signal SIGSEGV, Segmentation fault.
#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47
47 len = bvs->bv_len;
[Current thread is 1 (Thread 0x7f1db47fe640 (LWP 36576))]
(gdb) bt
#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47
#1 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:40
#2 slapi_value_set_berval (bval=0x7f1d00000000, value=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:322
#3 slapi_value_set_berval (value=value@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000) at ldap/servers/slapd/value.c:317
#4 0x00007f1e48b7d787 in value_init (v=v@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0)
at ldap/servers/slapd/value.c:179
#5 0x00007f1e48b7d884 in value_new (bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0) at ldap/servers/slapd/value.c:158
#6 0x00007f1e48b7ddb7 in slapi_value_dup (v=0x7f1d00000000) at ldap/servers/slapd/value.c:147
#7 0x00007f1e48b7e262 in valueset_set_valueset (vs2=0x7f1d502b5218, vs1=0x7f1da2c5b358) at ldap/servers/slapd/valueset.c:1244
#8 valueset_set_valueset (vs1=0x7f1da2c5b358, vs2=0x7f1d502b5218) at ldap/servers/slapd/valueset.c:1220
#9 0x00007f1e48add4af in slapi_attr_dup (attr=0x7f1d502b51e0) at ldap/servers/slapd/attr.c:396
#10 0x00007f1e48af0f60 in slapi_entry_dup (e=0x7f1da2c19000) at ldap/servers/slapd/entry.c:2036
#11 0x00007f1e442c734e in ldbm_back_modify (pb=0x7f1da2c00000) at ldap/servers/slapd/back-ldbm/ldbm_modify.c:741
#12 0x00007f1e48b30076 in op_shared_modify (pb=pb@entry=0x7f1da2c00000, pw_change=pw_change@entry=0, old_pw=0x0)
at ldap/servers/slapd/modify.c:1079
#13 0x00007f1e48b30ced in do_modify (pb=pb@entry=0x7f1da2c00000) at ldap/servers/slapd/modify.c:377
#14 0x000055e990e2fd1c in connection_dispatch_operation (pb=0x7f1da2c00000, op=<optimized out>, conn=<optimized out>)
at ldap/servers/slapd/connection.c:672
#15 connection_threadmain (arg=<optimized out>) at ldap/servers/slapd/connection.c:1955
#16 0x00007f1e48839bd4 in _pt_root (arg=0x7f1e439d9500) at pthreads/../../../../nspr/pr/src/pthreads/ptthread.c:191
#17 0x00007f1e4868a19a in start_thread (arg=<optimized out>) at pthread_create.c:443
#18 0x00007f1e4870f100 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
```
The fix changed from always setting `v_csnset = NULL` to only freeing it
inside the if-block.
Fix Description:
Keep `csnset_free()` outside the if-block to handle all values, not just
those matching the condtion.
Related: https://github.com/389ds/389-ds-base/issues/7121
Reviewed by: @progier389, @droideck (Thanks!)
---
ldap/servers/slapd/entrywsi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/entrywsi.c b/ldap/servers/slapd/entrywsi.c
index e1bdc1bab..0d044092d 100644
--- a/ldap/servers/slapd/entrywsi.c
+++ b/ldap/servers/slapd/entrywsi.c
@@ -1185,8 +1185,8 @@ resolve_attribute_state_deleted_to_present(Slapi_Entry *e, Slapi_Attr *a, Slapi_
if ((csn_compare(vucsn, deletedcsn) >= 0) ||
value_distinguished_at_csn(e, a, valuestoupdate[i], deletedcsn)) {
entry_deleted_value_to_present_value(a, valuestoupdate[i]);
- csnset_free(&valuestoupdate[i]->v_csnset);
}
+ csnset_free(&valuestoupdate[i]->v_csnset);
}
}
}
--
2.52.0

View File

@ -1,551 +0,0 @@
From 86b15d688949197a9efe3d5fc7a7eadcdd46e115 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Tue, 16 Dec 2025 15:48:35 -0800
Subject: [PATCH] Issue 7150 - Compressed access log rotations skipped,
accesslog-list out of sync (#7151)
Description: Accept `.gz`-suffixed rotated log filenames when
rebuilding rotation info and checking previous logs, preventing
compressed rotations from being dropped from the internal list.
Add regression tests to stress log rotation with compression,
verify `nsslapd-accesslog-list` stays in sync, and guard against
crashes when flushing buffered logs during rotation.
Minor doc fix in test.
Fixes: https://github.com/389ds/389-ds-base/issues/7150
Reviewed by: @progier389 (Thanks!)
---
.../suites/logging/log_flush_rotation_test.py | 341 +++++++++++++++++-
ldap/servers/slapd/log.c | 99 +++--
2 files changed, 402 insertions(+), 38 deletions(-)
diff --git a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
index b33a622e1..864ba9c5d 100644
--- a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
+++ b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
@@ -6,6 +6,7 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
#
+import glob
import os
import logging
import time
@@ -13,14 +14,351 @@ import pytest
from lib389._constants import DEFAULT_SUFFIX, PW_DM
from lib389.tasks import ImportTask
from lib389.idm.user import UserAccounts
+from lib389.idm.domain import Domain
+from lib389.idm.directorymanager import DirectoryManager
from lib389.topologies import topology_st as topo
log = logging.getLogger(__name__)
+def remove_rotated_access_logs(inst):
+ """
+ Remove all rotated access log files to start fresh for each test.
+ This prevents log files from previous tests affecting current test results.
+ """
+ log_dir = inst.get_log_dir()
+ patterns = [
+ f'{log_dir}/access.2*', # Uncompressed rotated logs
+ f'{log_dir}/access.*.gz', # Compressed rotated logs
+ ]
+ for pattern in patterns:
+ for log_file in glob.glob(pattern):
+ try:
+ os.remove(log_file)
+ log.info(f"Removed old log file: {log_file}")
+ except OSError as e:
+ log.warning(f"Could not remove {log_file}: {e}")
+
+
+def reset_access_log_config(inst):
+ """
+ Reset access log configuration to default values.
+ """
+ inst.config.set('nsslapd-accesslog-compress', 'off')
+ inst.config.set('nsslapd-accesslog-maxlogsize', '100')
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', '10')
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
+ inst.config.set('nsslapd-accesslog-logbuffering', 'on')
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
+ inst.config.set('nsslapd-accesslog-logminfreediskspace', '5')
+
+
+def generate_heavy_load(inst, suffix, iterations=50):
+ """
+ Generate heavy LDAP load to fill access log quickly.
+ Performs multiple operations: searches, modifies, binds to populate logs.
+ """
+ for i in range(iterations):
+ suffix.replace('description', f'iteration_{i}')
+ suffix.get_attr_val('description')
+
+
+def count_access_logs(log_dir, compressed_only=False):
+ """
+ Count access log files in the log directory.
+ Returns count of rotated access logs (not including the active 'access' file).
+ """
+ if compressed_only:
+ pattern = f'{log_dir}/access.*.gz'
+ else:
+ pattern = f'{log_dir}/access.2*'
+ log_files = glob.glob(pattern)
+ return len(log_files)
+
+
+def test_log_pileup_with_compression(topo):
+ """Test that log rotation properly deletes old logs when compression is enabled.
+
+ :id: fa1bfce8-b6d3-4520-a0a8-bead14fa5838
+ :setup: Standalone Instance
+ :steps:
+ 1. Clean up existing rotated logs and reset configuration
+ 2. Enable access log compression
+ 3. Set strict log limits (small maxlogsperdir)
+ 4. Disable log expiration to test count-based deletion
+ 5. Generate heavy load to create many log rotations
+ 6. Verify log count does not exceed maxlogsperdir limit
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. Log count should be at or below maxlogsperdir + small buffer
+ """
+
+ inst = topo.standalone
+ suffix = Domain(inst, DEFAULT_SUFFIX)
+ log_dir = inst.get_log_dir()
+
+ # Clean up before test
+ remove_rotated_access_logs(inst)
+ reset_access_log_config(inst)
+ inst.restart()
+
+ max_logs = 5
+ inst.config.set('nsslapd-accesslog-compress', 'on')
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', str(max_logs))
+ inst.config.set('nsslapd-accesslog-maxlogsize', '1') # 1MB to trigger rotation
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
+ inst.config.set('nsslapd-accesslog-logbuffering', 'off')
+
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
+
+ inst.config.set('nsslapd-accesslog-logminfreediskspace', '5')
+
+ inst.restart()
+ time.sleep(2)
+
+ target_logs = max_logs * 3
+ for i in range(target_logs):
+ log.info(f"Generating load for log rotation {i+1}/{target_logs}")
+ generate_heavy_load(inst, suffix, iterations=150)
+ time.sleep(1) # Wait for rotation
+
+ time.sleep(3)
+
+ logs_on_disk = count_access_logs(log_dir)
+ log.info(f"Configured maxlogsperdir: {max_logs}")
+ log.info(f"Actual rotated logs on disk: {logs_on_disk}")
+
+ all_access_logs = glob.glob(f'{log_dir}/access*')
+ log.info(f"All access log files: {all_access_logs}")
+
+ max_allowed = max_logs + 2
+ assert logs_on_disk <= max_allowed, (
+ f"Log rotation failed to delete old files! "
+ f"Expected at most {max_allowed} rotated logs (maxlogsperdir={max_logs} + 2 buffer), "
+ f"but found {logs_on_disk}. The server has lost track of the file list."
+ )
+
+
+@pytest.mark.parametrize("compress_enabled", ["on", "off"])
+def test_accesslog_list_mismatch(topo, compress_enabled):
+ """Test that nsslapd-accesslog-list stays synchronized with actual log files.
+
+ :id: 0a8a46a6-cae7-43bd-8b64-5e3481480cd3
+ :parametrized: yes
+ :setup: Standalone Instance
+ :steps:
+ 1. Clean up existing rotated logs and reset configuration
+ 2. Configure log rotation with compression enabled/disabled
+ 3. Generate activity to trigger multiple rotations
+ 4. Get the nsslapd-accesslog-list attribute
+ 5. Compare with actual files on disk
+ 6. Verify they match (accounting for .gz extension when enabled)
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Success
+ 6. The list attribute should match actual files on disk
+ """
+
+ inst = topo.standalone
+ suffix = Domain(inst, DEFAULT_SUFFIX)
+ log_dir = inst.get_log_dir()
+ compression_on = compress_enabled == "on"
+
+ # Clean up before test
+ remove_rotated_access_logs(inst)
+ reset_access_log_config(inst)
+ inst.restart()
+
+ inst.config.set('nsslapd-accesslog-compress', compress_enabled)
+ inst.config.set('nsslapd-accesslog-maxlogsize', '1')
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', '10')
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
+ inst.config.set('nsslapd-accesslog-logbuffering', 'off')
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
+
+ inst.restart()
+ time.sleep(2)
+
+ for i in range(15):
+ suffix_note = "(no compression)" if not compression_on else ""
+ log.info(f"Generating load for rotation {i+1}/15 {suffix_note}")
+ generate_heavy_load(inst, suffix, iterations=150)
+ time.sleep(1)
+
+ time.sleep(3)
+
+ accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list')
+ log.info(f"nsslapd-accesslog-list entries (compress={compress_enabled}): {len(accesslog_list)}")
+ log.info(f"nsslapd-accesslog-list (compress={compress_enabled}): {accesslog_list}")
+
+ disk_files = glob.glob(f'{log_dir}/access.2*')
+ log.info(f"Actual files on disk (compress={compress_enabled}): {len(disk_files)}")
+ log.info(f"Disk files (compress={compress_enabled}): {disk_files}")
+
+ disk_files_for_compare = set()
+ for fpath in disk_files:
+ if compression_on and fpath.endswith('.gz'):
+ disk_files_for_compare.add(fpath[:-3])
+ else:
+ disk_files_for_compare.add(fpath)
+
+ list_files_set = set(accesslog_list)
+ missing_from_disk = list_files_set - disk_files_for_compare
+ extra_on_disk = disk_files_for_compare - list_files_set
+
+ if missing_from_disk:
+ log.error(
+ f"[compress={compress_enabled}] Files in list but NOT on disk: {missing_from_disk}"
+ )
+ if extra_on_disk:
+ log.warning(
+ f"[compress={compress_enabled}] Files on disk but NOT in list: {extra_on_disk}"
+ )
+
+ assert not missing_from_disk, (
+ f"nsslapd-accesslog-list mismatch (compress={compress_enabled})! "
+ f"Files listed but missing from disk: {missing_from_disk}. "
+ f"This indicates the server's internal list is out of sync with actual files."
+ )
+
+ if len(extra_on_disk) > 2:
+ log.warning(
+ f"Potential log tracking issue (compress={compress_enabled}): "
+ f"{len(extra_on_disk)} files on disk are not tracked in the accesslog-list: "
+ f"{extra_on_disk}"
+ )
+
+
+def test_accesslog_list_mixed_compression(topo):
+ """Test that nsslapd-accesslog-list correctly tracks both compressed and uncompressed logs.
+
+ :id: 11b088cd-23be-407d-ad16-4ce2e12da09e
+ :setup: Standalone Instance
+ :steps:
+ 1. Clean up existing rotated logs and reset configuration
+ 2. Create rotated logs with compression OFF
+ 3. Enable compression and create more rotated logs
+ 4. Get the nsslapd-accesslog-list attribute
+ 5. Compare with actual files on disk
+ 6. Verify all files are correctly tracked (uncompressed and compressed)
+ :expectedresults:
+ 1. Success
+ 2. Success - uncompressed rotated logs created
+ 3. Success - compressed rotated logs created
+ 4. Success
+ 5. Success
+ 6. The list should contain base filenames (without .gz) that
+ correspond to files on disk (either as-is or with .gz suffix)
+ """
+
+ inst = topo.standalone
+ suffix = Domain(inst, DEFAULT_SUFFIX)
+ log_dir = inst.get_log_dir()
+
+ # Clean up before test
+ remove_rotated_access_logs(inst)
+ reset_access_log_config(inst)
+ inst.restart()
+
+ inst.config.set('nsslapd-accesslog-compress', 'off')
+ inst.config.set('nsslapd-accesslog-maxlogsize', '1')
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', '20')
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
+ inst.config.set('nsslapd-accesslog-logbuffering', 'off')
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
+
+ inst.restart()
+ time.sleep(2)
+
+ for i in range(15):
+ log.info(f"Generating load for uncompressed rotation {i+1}/15")
+ generate_heavy_load(inst, suffix, iterations=150)
+ time.sleep(1)
+
+ time.sleep(2)
+
+ # Check what we have so far
+ uncompressed_files = glob.glob(f'{log_dir}/access.2*')
+ log.info(f"Files on disk after uncompressed phase: {uncompressed_files}")
+
+ inst.config.set('nsslapd-accesslog-compress', 'on')
+ inst.restart()
+ time.sleep(2)
+
+ for i in range(15):
+ log.info(f"Generating load for compressed rotation {i+1}/15")
+ generate_heavy_load(inst, suffix, iterations=150)
+ time.sleep(1)
+
+ time.sleep(3)
+
+ accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list')
+
+ disk_files = glob.glob(f'{log_dir}/access.2*')
+
+ log.info(f"nsslapd-accesslog-list entries: {len(accesslog_list)}")
+ log.info(f"nsslapd-accesslog-list: {sorted(accesslog_list)}")
+ log.info(f"Actual files on disk: {len(disk_files)}")
+ log.info(f"Disk files: {sorted(disk_files)}")
+
+ compressed_on_disk = [f for f in disk_files if f.endswith('.gz')]
+ uncompressed_on_disk = [f for f in disk_files if not f.endswith('.gz')]
+ log.info(f"Compressed files on disk: {compressed_on_disk}")
+ log.info(f"Uncompressed files on disk: {uncompressed_on_disk}")
+
+ list_files_set = set(accesslog_list)
+
+ disk_files_base = set()
+ for fpath in disk_files:
+ if fpath.endswith('.gz'):
+ disk_files_base.add(fpath[:-3]) # Strip .gz
+ else:
+ disk_files_base.add(fpath)
+
+ missing_from_disk = list_files_set - disk_files_base
+
+ extra_on_disk = disk_files_base - list_files_set
+
+ if missing_from_disk:
+ log.error(f"Files in list but NOT on disk: {missing_from_disk}")
+ if extra_on_disk:
+ log.warning(f"Files on disk but NOT in list: {extra_on_disk}")
+
+ assert not missing_from_disk, (
+ f"nsslapd-accesslog-list contains stale entries! "
+ f"Files in list but not on disk (as base or .gz): {missing_from_disk}"
+ )
+
+ for list_file in accesslog_list:
+ exists_uncompressed = os.path.exists(list_file)
+ exists_compressed = os.path.exists(list_file + '.gz')
+ assert exists_uncompressed or exists_compressed, (
+ f"File in accesslog-list does not exist on disk: {list_file} "
+ f"(checked both {list_file} and {list_file}.gz)"
+ )
+ if exists_compressed and not exists_uncompressed:
+ log.info(f" {list_file} -> exists as .gz (compressed)")
+ elif exists_uncompressed:
+ log.info(f" {list_file} -> exists (uncompressed)")
+
+ if len(extra_on_disk) > 1:
+ log.warning(
+ f"Some files on disk are not tracked in accesslog-list: {extra_on_disk}"
+ )
+
+ log.info("Mixed compression test completed successfully")
+
+
def test_log_flush_and_rotation_crash(topo):
- """Make sure server does not crash whening flushing a buffer and rotating
+ """Make sure server does not crash when flushing a buffer and rotating
the log at the same time
:id: d4b0af2f-48b2-45f5-ae8b-f06f692c3133
@@ -36,6 +374,7 @@ def test_log_flush_and_rotation_crash(topo):
3. Success
4. Success
"""
+ # NOTE: This test is placed last as it may affect the suffix state.
inst = topo.standalone
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 6f57a3d9c..ca8d481e5 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -135,6 +135,7 @@ static void vslapd_log_emergency_error(LOGFD fp, const char *msg, int locked);
static int get_syslog_loglevel(int loglevel);
static void log_external_libs_debug_openldap_print(char *buffer);
static int log__fix_rotationinfof(char *pathname);
+static int log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed);
static int
get_syslog_loglevel(int loglevel)
@@ -410,7 +411,7 @@ g_log_init()
loginfo.log_security_fdes = NULL;
loginfo.log_security_file = NULL;
loginfo.log_securityinfo_file = NULL;
- loginfo.log_numof_access_logs = 1;
+ loginfo.log_numof_security_logs = 1;
loginfo.log_security_logchain = NULL;
loginfo.log_security_buffer = log_create_buffer(LOG_BUFFER_MAXSIZE);
loginfo.log_security_compress = cfg->securitylog_compress;
@@ -3311,7 +3312,7 @@ log__open_accesslogfile(int logfile_state, int locked)
}
} else if (loginfo.log_access_compress) {
if (compress_log_file(newfile, loginfo.log_access_mode) != 0) {
- slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile",
+ slapi_log_err(SLAPI_LOG_ERR, "log__open_accesslogfile",
"failed to compress rotated access log (%s)\n",
newfile);
} else {
@@ -4710,6 +4711,50 @@ log__delete_rotated_logs()
loginfo.log_error_logchain = NULL;
}
+/*
+ * log__validate_rotated_logname
+ *
+ * Validates that a log filename timestamp suffix matches the expected format:
+ * YYYYMMDD-HHMMSS (15 chars) or YYYYMMDD-HHMMSS.gz (18 chars) for compressed files.
+ * Uses regex pattern: ^[0-9]{8}-[0-9]{6}(\.gz)?$
+ *
+ * \param timestamp_str The timestamp portion of the log filename (after the first '.')
+ * \param is_compressed Output parameter set to PR_TRUE if the file has .gz suffix
+ * \return 1 if valid, 0 if invalid
+ */
+static int
+log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed)
+{
+ Slapi_Regex *re = NULL;
+ char *re_error = NULL;
+ int rc = 0;
+
+ /* Match YYYYMMDD-HHMMSS with optional .gz suffix */
+ static const char *pattern = "^[0-9]{8}-[0-9]{6}(\\.gz)?$";
+
+ *is_compressed = PR_FALSE;
+
+ re = slapi_re_comp(pattern, &re_error);
+ if (re == NULL) {
+ slapi_log_err(SLAPI_LOG_ERR, "log__validate_rotated_logname",
+ "Failed to compile regex: %s\n", re_error ? re_error : "unknown error");
+ slapi_ch_free_string(&re_error);
+ return 0;
+ }
+
+ rc = slapi_re_exec_nt(re, timestamp_str);
+ if (rc == 1) {
+ /* Check if compressed by looking for .gz suffix */
+ size_t len = strlen(timestamp_str);
+ if (len >= 3 && strcmp(timestamp_str + len - 3, ".gz") == 0) {
+ *is_compressed = PR_TRUE;
+ }
+ }
+
+ slapi_re_free(re);
+ return rc == 1 ? 1 : 0;
+}
+
#define ERRORSLOG 1
#define ACCESSLOG 2
#define AUDITLOG 3
@@ -4792,31 +4837,19 @@ log__fix_rotationinfof(char *pathname)
}
} else if (0 == strncmp(log_type, dirent->name, strlen(log_type)) &&
(p = strchr(dirent->name, '.')) != NULL &&
- NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 */
+ NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */
{
struct logfileinfo *logp;
- char *q;
- int ignoreit = 0;
-
- for (q = ++p; q && *q; q++) {
- if (*q != '-' &&
- *q != '.' && /* .gz */
- *q != 'g' &&
- *q != 'z' &&
- !isdigit(*q))
- {
- ignoreit = 1;
- }
- }
- if (ignoreit || (q - p != 15)) {
+ PRBool is_compressed = PR_FALSE;
+
+ /* Skip the '.' to get the timestamp portion */
+ p++;
+ if (!log__validate_rotated_logname(p, &is_compressed)) {
continue;
}
logp = (struct logfileinfo *)slapi_ch_malloc(sizeof(struct logfileinfo));
logp->l_ctime = log_reverse_convert_time(p);
- logp->l_compressed = PR_FALSE;
- if (strcmp(p + strlen(p) - 3, ".gz") == 0) {
- logp->l_compressed = PR_TRUE;
- }
+ logp->l_compressed = is_compressed;
PR_snprintf(rotated_log, rotated_log_len, "%s/%s",
logsdir, dirent->name);
@@ -4982,23 +5015,15 @@ log__check_prevlogs(FILE *fp, char *pathname)
for (dirent = PR_ReadDir(dirptr, dirflags); dirent;
dirent = PR_ReadDir(dirptr, dirflags)) {
if (0 == strncmp(log_type, dirent->name, strlen(log_type)) &&
- (p = strrchr(dirent->name, '.')) != NULL &&
- NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 */
- char *q;
- int ignoreit = 0;
-
- for (q = ++p; q && *q; q++) {
- if (*q != '-' &&
- *q != '.' && /* .gz */
- *q != 'g' &&
- *q != 'z' &&
- !isdigit(*q))
- {
- ignoreit = 1;
- }
- }
- if (ignoreit || (q - p != 15))
+ (p = strchr(dirent->name, '.')) != NULL &&
+ NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */
+ PRBool is_compressed = PR_FALSE;
+
+ /* Skip the '.' to get the timestamp portion */
+ p++;
+ if (!log__validate_rotated_logname(p, &is_compressed)) {
continue;
+ }
fseek(fp, 0, SEEK_SET);
buf[BUFSIZ - 1] = '\0';
--
2.52.0

View File

@ -1,93 +0,0 @@
From 524cae18721234e1bc8d958c89684008d62e6b30 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Thu, 5 Feb 2026 15:33:08 +0000
Subject: [PATCH] Issue 7224 - CI Test - Simplify
test_reserve_descriptor_validation (#7225)
Description:
Previously, the test_reserve_descriptor_validation CItest calculated
the expected number of file descriptors based on backends, indexes,
SSL/FIPS mode, and compared it to the value returned by the server.
This approach is fragile, especially in FIPS mode.
Fix:
The test has been updated to simply verify that the server corrects
the configured nsslapd-reservedescriptors value if it is set too low,
instead of calculating the expected total.
Fixes: https://github.com/389ds/389-ds-base/issues/7224
Reviewed by: @bsimonova (Thank you)
---
.../suites/resource_limits/fdlimits_test.py | 36 +++++++------------
1 file changed, 13 insertions(+), 23 deletions(-)
diff --git a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
index edcae28a5..9d8fdec52 100644
--- a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
+++ b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
@@ -27,7 +27,7 @@ RESRV_FD_ATTR = "nsslapd-reservedescriptors"
GLOBAL_LIMIT = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
SYSTEMD_LIMIT = ensure_str(check_output("systemctl show -p LimitNOFILE dirsrv@standalone1".split(" ")).strip()).split('=')[1]
CUSTOM_VAL = str(int(SYSTEMD_LIMIT) - 10)
-RESRV_DESC_VAL = str(10)
+RESRV_DESC_VAL_LOW = 10
TOO_HIGH_VAL = str(GLOBAL_LIMIT * 2)
TOO_HIGH_VAL2 = str(int(SYSTEMD_LIMIT) * 2)
TOO_LOW_VAL = "0"
@@ -86,40 +86,30 @@ def test_reserve_descriptor_validation(topology_st):
:id: 9bacdbcc-7754-4955-8a56-1d8c82bce274
:setup: Standalone Instance
:steps:
- 1. Set attr nsslapd-reservedescriptors to a low value of RESRV_DESC_VAL (10)
+ 1. Set attr nsslapd-reservedescriptors to a low value (10)
2. Verify low value has been set
3. Restart instance (On restart the reservedescriptor attr will be validated)
- 4. Check updated value for nsslapd-reservedescriptors attr
+ 4. Verify corrected value for nsslapd-reservedescriptors > low value
:expectedresults:
1. Success
- 2. A value of RESRV_DESC_VAL (10) is returned
+ 2. A value of RESRV_DESC_VAL_LOW (10) is returned
3. Success
- 4. A value of STANDALONE_INST_RESRV_DESCS (55) is returned
+ 4. Corrected value for nsslapd-reservedescriptors > low value
"""
- # Set nsslapd-reservedescriptors to a low value (RESRV_DESC_VAL:10)
- topology_st.standalone.config.set(RESRV_FD_ATTR, RESRV_DESC_VAL)
- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
- assert resrv_fd == RESRV_DESC_VAL
+ # Set nsslapd-reservedescriptors to a low value (10)
+ topology_st.standalone.config.set(RESRV_FD_ATTR, str(RESRV_DESC_VAL_LOW))
+ resrv_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
+ assert resrv_fd == RESRV_DESC_VAL_LOW
# An instance restart triggers a validation of the configured nsslapd-reservedescriptors attribute
topology_st.standalone.restart()
- """
- A standalone instance contains a single backend with default indexes
- so we only check these. TODO add tests for repl, chaining, PTA, SSL
- """
- STANDALONE_INST_RESRV_DESCS = 20 # 20 = Reserve descriptor constant
- backends = Backends(topology_st.standalone)
- STANDALONE_INST_RESRV_DESCS += (len(backends.list()) * 4) # 4 = Backend descriptor constant
- for be in backends.list() :
- STANDALONE_INST_RESRV_DESCS += len(be.get_indexes().list())
-
- # Varify reservedescriptors has been updated
- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
- assert resrv_fd == str(STANDALONE_INST_RESRV_DESCS)
+ # Get the corrected value
+ corrected_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
+ assert corrected_fd > RESRV_DESC_VAL_LOW
- log.info("test_reserve_descriptor_validation PASSED")
+ log.info(f"test_reserve_descriptor_validation PASSED (corrected from {RESRV_DESC_VAL_LOW} to {corrected_fd})")
@pytest.mark.skipif(ds_is_older("1.4.1.2"), reason="Not implemented")
def test_reserve_descriptors_high(topology_st):
--
2.52.0

View File

@ -1,92 +0,0 @@
From 91e03f431d8ee0b1d316ebd7add232bf49360db2 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Thu, 12 Feb 2026 10:42:09 +0000
Subject: [PATCH] Issue 7231 - Sync repl tests fail in FIPS mode due to non
FIPS compliant crypto (#7232)
Description:
Several sync_repl tests fail when running on a FIPS enabled system. The failures
are caused by the sync repl client (Sync_persist), using TLS options and ciphers
that are not FIPS compatible.
Fix:
Update the sync repl client to use FIPS approved TLS version.
Fixes: https://github.com/389ds/389-ds-base/issues/7231
Reviewed by: @progier389, @droideck (Thank you)
---
.../tests/suites/syncrepl_plugin/basic_test.py | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
index c22331eb5..1e9a530bb 100644
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
@@ -20,7 +20,7 @@ from lib389.idm.group import Groups
from lib389.topologies import topology_st as topology
from lib389.topologies import topology_m2 as topo_m2
from lib389.paths import Paths
-from lib389.utils import ds_is_older
+from lib389.utils import ds_is_older, is_fips
from lib389.plugins import RetroChangelogPlugin, ContentSyncPlugin, AutoMembershipPlugin, MemberOfPlugin, MemberOfSharedConfig, AutoMembershipDefinitions, MEPTemplates, MEPConfigs, ManagedEntriesPlugin, MEPTemplate
from lib389._constants import *
@@ -214,6 +214,12 @@ class Sync_persist(threading.Thread, ReconnectLDAPObject, SyncreplConsumer):
def run(self):
"""Start a sync repl client"""
ldap_connection = TestSyncer(self.inst.toLDAPURL())
+ ldap_connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
+ ldap_connection.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join(self.inst.get_config_dir(), "ca.crt"))
+ if is_fips():
+ ldap_connection.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, ldap.OPT_X_TLS_PROTOCOL_TLS1_2)
+ ldap_connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+
ldap_connection.simple_bind_s('cn=directory manager', 'password')
ldap_search = ldap_connection.syncrepl_search(
"dc=example,dc=com",
@@ -253,6 +259,7 @@ def test_sync_repl_mep(topology, request):
5. Success
"""
inst = topology[0]
+ inst.enable_tls()
# Enable/configure retroCL
plugin = RetroChangelogPlugin(inst)
@@ -338,6 +345,7 @@ def test_sync_repl_cookie(topology, init_sync_repl_plugins, request):
5.: succeeds
"""
inst = topology[0]
+ inst.enable_tls()
# create a sync repl client and wait 5 seconds to be sure it is running
sync_repl = Sync_persist(inst)
@@ -404,6 +412,8 @@ def test_sync_repl_cookie_add_del(topology, init_sync_repl_plugins, request):
6.: succeeds
"""
inst = topology[0]
+ inst.enable_tls()
+
# create a sync repl client and wait 5 seconds to be sure it is running
sync_repl = Sync_persist(inst)
sync_repl.start()
@@ -547,6 +557,7 @@ def test_sync_repl_cenotaph(topo_m2, request):
5. Should succeeds
"""
m1 = topo_m2.ms["supplier1"]
+ m1.enable_tls()
# Enable/configure retroCL
plugin = RetroChangelogPlugin(m1)
plugin.disable()
@@ -605,7 +616,7 @@ def test_sync_repl_dynamic_plugin(topology, request):
3. Should succeeds
4. Should succeeds
"""
-
+ topology.standalone.enable_tls()
# Reset the instance in a default config
# Disable content sync plugin
topology.standalone.plugins.disable(name=PLUGIN_REPL_SYNC)
--
2.52.0

View File

@ -1,33 +0,0 @@
From bd57467f6efd8e398eb2e608331711bdd571d3ac Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 12 Feb 2026 09:58:54 -0500
Subject: [PATCH] Issue 7248 - CLI - attribute uniqueness - fix usage for
exclude subtree option
Description:
Fix typo in usage message for the exclude subtree option
relates: https://github.com/389ds/389-ds-base/issues/7248
Reviewed by: progier (Thanks!)
---
src/lib389/lib389/cli_conf/plugins/attruniq.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py
index bc925eb1c..26ca5d819 100644
--- a/src/lib389/lib389/cli_conf/plugins/attruniq.py
+++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py
@@ -127,7 +127,7 @@ def _add_parser_args(parser):
help='Sets the DN under which the plug-in checks for uniqueness of '
'the attributes value. This attribute is multi-valued (uniqueness-subtrees)')
parser.add_argument('--exclude-subtree', nargs='+',
- help='Sets subtrees that should not excludedfrom attribute uniqueness. '
+ help='Sets subtrees that should be excluded from attribute uniqueness checks. '
'This attribute is multi-valued (uniqueness-exclude-subtrees)')
parser.add_argument('--across-all-subtrees', choices=['on', 'off'], type=str.lower,
help='If enabled (on), the plug-in checks that the attribute is unique across all subtrees '
--
2.52.0

View File

@ -1,201 +0,0 @@
From 180329d9ca672306f8e90d599890be3a6a8aa95c Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 12 Feb 2026 11:13:45 -0500
Subject: [PATCH] Issue - CLI - dsctl db2index needs some hardening with MBD
Description:
The usage for dsctl db2index was confusing. The way the attr options and
backend name were displayed it looks like the backend name could come after
the attributes, but instead the backend name was treated as an attribute.
Instead make the backend name required, and change the attribute naming to
require individual options instead of a list of values.
Relates: https://github.com/389ds/389-ds-base/issues/7250
Reviewed by: progier(Thanks!)
---
.../tests/suites/import/import_test.py | 2 +-
src/lib389/lib389/__init__.py | 40 ++++++-------------
src/lib389/lib389/cli_ctl/dbtasks.py | 37 +++++++----------
3 files changed, 27 insertions(+), 52 deletions(-)
diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py
index 18caec633..058a4b3c7 100644
--- a/dirsrvtests/tests/suites/import/import_test.py
+++ b/dirsrvtests/tests/suites/import/import_test.py
@@ -512,7 +512,7 @@ def test_entry_with_escaped_characters_fails_to_import_and_index(topo, _import_c
count += 1
# Now re-index the database
topo.standalone.stop()
- topo.standalone.db2index()
+ topo.standalone.db2index(bename="userroot")
topo.standalone.start()
# Should not return error.
assert not topo.standalone.searchErrorsLog('error')
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 39a2852e5..ec47987be 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -2985,7 +2985,7 @@ class DirSrv(SimpleLDAPObject, object):
return True
- def db2index(self, bename=None, suffixes=None, attrs=None, vlvTag=None):
+ def db2index(self, bename, suffixes=None, attrs=None, vlvTag=None):
"""
@param bename - The backend name to reindex
@param suffixes - List/tuple of suffixes to reindex, currently unused
@@ -2998,34 +2998,18 @@ class DirSrv(SimpleLDAPObject, object):
if self.status():
self.log.error("db2index: Can not operate while directory server is running")
return False
- cmd = [prog, ]
- # No backend specified, do an upgrade on all backends
- # Backend and no attrs specified, reindex with all backend indexes
- # Backend and attr/s specified, reindex backend with attr/s
- if bename:
- cmd.append('db2index')
- cmd.append('-n')
- cmd.append(bename)
- if attrs:
- for attr in attrs:
- cmd.append('-t')
- cmd.append(attr)
- else:
- dse_ldif = DSEldif(self)
- indexes = dse_ldif.get_indexes(bename)
- if indexes:
- for idx in indexes:
- cmd.append('-t')
- cmd.append(idx)
+ cmd = [prog, 'db2index', '-n', bename, '-D', self.get_config_dir()]
+ if attrs:
+ for attr in attrs:
+ cmd.append('-t')
+ cmd.append(attr)
else:
- cmd.append('upgradedb')
- cmd.append('-a')
- now = datetime.now().isoformat()
- cmd.append(os.path.join(self.get_bak_dir(), 'reindex_%s' % now))
- cmd.append('-f')
-
- cmd.append('-D')
- cmd.append(self.get_config_dir())
+ dse_ldif = DSEldif(self)
+ indexes = dse_ldif.get_indexes(bename)
+ if indexes:
+ for idx in indexes:
+ cmd.append('-t')
+ cmd.append(idx)
try:
result = subprocess.check_output(cmd, encoding='utf-8')
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
index 16da966d1..cd96cdaf7 100644
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
@@ -26,32 +26,18 @@ class IndexOrdering(Enum):
def dbtasks_db2index(inst, log, args):
- rtn = False
- if not args.backend:
- if not inst.db2index():
- rtn = False
- else:
- rtn = True
- elif args.backend and not args.attr:
- if not inst.db2index(bename=args.backend):
- rtn = False
- else:
- rtn = True
+ inst.log = log
+ if not inst.db2index(bename=args.backend, attrs=args.attr):
+ log.fatal("db2index failed")
+ return False
else:
- if not inst.db2index(bename=args.backend, attrs=args.attr):
- rtn = False
- else:
- rtn = True
- if rtn:
log.info("db2index successful")
- return rtn
- else:
- log.fatal("db2index failed")
- return rtn
+ return True
def dbtasks_db2bak(inst, log, args):
# Needs an output name?
+ inst.log = log
if not inst.db2bak(args.archive):
log.fatal("db2bak failed")
return False
@@ -61,6 +47,7 @@ def dbtasks_db2bak(inst, log, args):
def dbtasks_bak2db(inst, log, args):
# Needs the archive to restore.
+ inst.log = log
if not inst.bak2db(args.archive):
log.fatal("bak2db failed")
return False
@@ -70,6 +57,7 @@ def dbtasks_bak2db(inst, log, args):
def dbtasks_db2ldif(inst, log, args):
# If export filename is provided, check if file path exists
+ inst.log = log
if args.ldif:
path = Path(args.ldif)
parent = path.parent.absolute()
@@ -88,6 +76,7 @@ def dbtasks_db2ldif(inst, log, args):
def dbtasks_ldif2db(inst, log, args):
# Check if ldif file exists
+ inst.log = log
if not os.path.exists(args.ldif):
raise ValueError("The LDIF file does not exist: " + args.ldif)
@@ -103,6 +92,7 @@ def dbtasks_ldif2db(inst, log, args):
def dbtasks_backups(inst, log, args):
+ inst.log = log
if args.delete:
# Delete backup
inst.del_backup(args.delete[0])
@@ -117,6 +107,7 @@ def dbtasks_backups(inst, log, args):
def dbtasks_ldifs(inst, log, args):
+ inst.log = log
if args.delete:
# Delete LDIF file
inst.del_ldif(args.delete[0])
@@ -131,6 +122,7 @@ def dbtasks_ldifs(inst, log, args):
def dbtasks_verify(inst, log, args):
+ inst.log = log
if not inst.dbverify(bename=args.backend):
log.fatal("dbverify failed")
return False
@@ -521,9 +513,8 @@ def dbtasks_index_check(inst, log, args):
def create_parser(subcommands):
db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
- # db2index_parser.add_argument('suffix', help="The suffix to reindex. IE dc=example,dc=com.")
- db2index_parser.add_argument('backend', nargs="?", help="The backend to reindex. IE userRoot", default=False)
- db2index_parser.add_argument('--attr', nargs="*", help="The attribute's to reindex. IE --attr aci cn givenname", default=False)
+ db2index_parser.add_argument('backend', help="The backend to reindex. IE userRoot")
+ db2index_parser.add_argument('--attr', action='append', help="An attribute to reindex. IE: --attr member --attr cn ...")
db2index_parser.set_defaults(func=dbtasks_db2index)
db2bak_parser = subcommands.add_parser('db2bak', help="Initialise a BDB backup of the database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
--
2.52.0

View File

@ -1,41 +0,0 @@
From c831a7af2afb7cfd2eb958476a7a8d421ac5d339 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 13 Feb 2026 15:38:52 +0100
Subject: [PATCH] Issue 7184 - (2nd) argparse.HelpFormatter
_format_actions_usage() is deprecated (#7257)
Description:
`_format_actions_usage()` was also removed in Python 3.14.3.
Replace version check with `isinstance()` to handle the return type of
`_get_actions_usage_parts()` more robustly across Python versions.
Relates: https://github.com/389ds/389-ds-base/issues/7184
Fixes: https://github.com/389ds/389-ds-base/issues/7253
Reviewed by: @progier389 (Thanks!)
---
src/lib389/lib389/cli_base/__init__.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
index f1055aadc..3af8a46e6 100644
--- a/src/lib389/lib389/cli_base/__init__.py
+++ b/src/lib389/lib389/cli_base/__init__.py
@@ -420,11 +420,11 @@ class CustomHelpFormatter(argparse.HelpFormatter):
else:
# Use _get_actions_usage_parts() for Python 3.13 and later
action_parts = self._get_actions_usage_parts(parent_arguments, [])
- if sys.version_info >= (3, 15):
- # Python 3.15 returns a tuple (list of actions, count of actions)
+ if isinstance(action_parts, tuple):
+ # Python 3.14.3+ and 3.15+ return a tuple (list of actions, count of actions)
formatted_options = ' '.join(action_parts[0])
else:
- # Python 3.13 and 3.14 return a list of actions
+ # Earlier versions return a list of actions
formatted_options = ' '.join(action_parts)
# If formatted_options already in usage - remove them
--
2.52.0

View File

@ -1,32 +0,0 @@
From a7f8eb79dde03ac771de2335a2acb4aed48483a2 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 13 Feb 2026 16:27:25 +0100
Subject: [PATCH] Issue 7213 - (2nd) MDB_BAD_VALSIZE error while handling VLV
(#7258)
Decription:
Disable test_vlv_long_attribute_value on BDB as it hangs sometimes in
CI, blocking other pipelines.
Relates: https://github.com/389ds/389-ds-base/issues/7213
Reviewed by: @progier389 (Thanks!)
---
dirsrvtests/tests/suites/vlv/regression_test.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
index 1cc03c303..94001af62 100644
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
@@ -1178,6 +1178,7 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
+@pytest.mark.skipif(get_default_db_lib() == "bdb", reason="Hangs on BDB")
def test_vlv_long_attribute_value(topology_st, request):
"""
Test VLV with an entry containing a very long attribute value (2K).
--
2.52.0

View File

@ -1,34 +0,0 @@
From 75b1213dfb94495160a735c2688cee03d6be5a9a Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 13 Feb 2026 16:58:24 +0100
Subject: [PATCH] Issue 7223 - Use lexicographical order for ancestorid (#7256)
Description:
`ldbm_instance_create_default_indexes()` configured ancestorid with
integerOrderingMatch in the in-memory attrinfo, but ancestorid on disk
might be using lexicographic ordering (data before the upgrade or after
ldif2db import).
Relates: https://github.com/389ds/389-ds-base/issues/7223
Reviewed by: @tbordaz (Thanks!)
---
ldap/servers/slapd/back-ldbm/instance.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
index 3fcdb5554..563cf96db 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -239,7 +239,7 @@ ldbm_instance_create_default_indexes(backend *be)
* ancestorid is special, there is actually no such attr type
* but we still want to use the attr index file APIs.
*/
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0);
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
slapi_entry_free(e);
}
--
2.52.0

View File

@ -1,89 +0,0 @@
From 7809104b9edb2d05ceee7ca819152f365cc1beb0 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 11 Feb 2026 15:51:47 -0500
Subject: [PATCH] Issue 7066/7052 - allow password history to be set to zero
and remove history
Description:
For local password policies the server was incorrectly rejecting updates that
set the value to zero. When password history is set to zero the old passwords
in the entry history are not cleaned as expected.
relates: https://github.com/389ds/389-ds-base/issues/7052
relates: https://github.com/389ds/389-ds-base/issues/7066
Reviewed by: progier(Thanks!)
---
.../tests/suites/password/pwp_history_test.py | 9 ++++++---
ldap/servers/slapd/modify.c | 2 +-
ldap/servers/slapd/pw.c | 13 ++++++++++++-
3 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py
index 8a15a1986..387824bc5 100644
--- a/dirsrvtests/tests/suites/password/pwp_history_test.py
+++ b/dirsrvtests/tests/suites/password/pwp_history_test.py
@@ -108,8 +108,12 @@ def test_history_is_not_overwritten(topology_st, user):
user.set('userpassword', USER_PWD)
-def test_basic(topology_st, user):
- """Test basic password policy history feature functionality
+@pytest.mark.parametrize('policy',
+ [(pytest.param('global')),
+ (pytest.param('subtree')),
+ (pytest.param('user'))])
+def test_basic(topology_st, user, policy):
+ """Test basic password policy history feature functionality with dynamic count reduction
:id: 83d74f7d-3036-4944-8839-1b40bbf265ff
:setup: Standalone instance, a test user
@@ -238,7 +242,6 @@ def test_basic(topology_st, user):
#
# Reset password by Directory Manager(admin reset)
- #
dm = DirectoryManager(topology_st.standalone)
dm.rebind()
time.sleep(.5)
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index b0066faf8..d76427886 100644
--- a/ldap/servers/slapd/modify.c
+++ b/ldap/servers/slapd/modify.c
@@ -87,7 +87,7 @@ static struct attr_value_check
{CONFIG_PW_WARNING_ATTRIBUTE, check_pw_duration_value, 0, -1},
{CONFIG_PW_MINLENGTH_ATTRIBUTE, attr_check_minmax, 2, 512},
{CONFIG_PW_MAXFAILURE_ATTRIBUTE, attr_check_minmax, 1, 32767},
- {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 1, 24},
+ {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 0, 24},
{CONFIG_PW_LOCKDURATION_ATTRIBUTE, check_pw_duration_value, -1, -1},
{CONFIG_PW_RESETFAILURECOUNT_ATTRIBUTE, check_pw_resetfailurecount_value, -1, -1},
{CONFIG_PW_GRACELIMIT_ATTRIBUTE, attr_check_minmax, 0, -1},
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index cda1c404f..12cf759ce 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -1532,7 +1532,18 @@ update_pw_history(Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_pw)
pwpolicy = new_passwdPolicy(pb, dn);
if (pwpolicy->pw_inhistory == 0){
- /* We are only enforcing the current password, just return */
+ /* We are only enforcing the current password, just return but first
+ * cleanup any old passwords in the history */
+ attribute.mod_type = "passwordHistory";
+ attribute.mod_op = LDAP_MOD_REPLACE;
+ attribute.mod_values = NULL;
+ list_of_mods[0] = &attribute;
+ list_of_mods[1] = NULL;
+ mod_pb = slapi_pblock_new();
+ slapi_modify_internal_set_pb_ext(mod_pb, sdn, list_of_mods, NULL, NULL, pw_get_componentID(), 0);
+ slapi_modify_internal_pb(mod_pb);
+ slapi_pblock_destroy(mod_pb);
+
return res;
}
--
2.52.0

View File

@ -1,260 +0,0 @@
From 4209ff65310c54b6ab55594984201b1214fb75b2 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 16 Feb 2026 16:52:52 -0500
Subject: [PATCH] Issue 7243 - UI - fix certificate table and modal
Description:
The certificate table was not handling sorting and pagination correctly, and
the add cert modal was not correctly selecting certs on disk
This was fixed upstream for the hot cetificate features, but only backporting
the table/modal changes to older branches
relates: https://github.com/389ds/389-ds-base/issues/7243
Reviewed by: spichugi(Thanks!)
---
.../src/lib/security/securityModals.jsx | 4 +-
.../src/lib/security/securityTables.jsx | 85 ++++++-------------
2 files changed, 29 insertions(+), 60 deletions(-)
diff --git a/src/cockpit/389-console/src/lib/security/securityModals.jsx b/src/cockpit/389-console/src/lib/security/securityModals.jsx
index a42aa50d6..c4af753c0 100644
--- a/src/cockpit/389-console/src/lib/security/securityModals.jsx
+++ b/src/cockpit/389-console/src/lib/security/securityModals.jsx
@@ -298,7 +298,9 @@ export class SecurityAddCertModal extends React.Component {
<FormSelect
value={selectCertName}
id="selectCertName"
- onChange={handleCertSelect}
+ onChange={(e, str) => {
+ handleCertSelect(str);
+ }}
aria-label="FormSelect Input"
className="ds-cert-select"
validated={selectValidated}
diff --git a/src/cockpit/389-console/src/lib/security/securityTables.jsx b/src/cockpit/389-console/src/lib/security/securityTables.jsx
index fce4cb04e..a6c64e6c1 100644
--- a/src/cockpit/389-console/src/lib/security/securityTables.jsx
+++ b/src/cockpit/389-console/src/lib/security/securityTables.jsx
@@ -4,7 +4,6 @@ import {
Grid,
GridItem,
Pagination,
- PaginationVariant,
SearchInput,
Tooltip,
} from '@patternfly/react-core';
@@ -94,10 +93,10 @@ class KeyTable extends React.Component {
];
handleSort(_event, index, direction) {
- const sortedRows = [...this.state.rows].sort((a, b) =>
+ const sortedRows = [...this.state.rows].sort((a, b) =>
(a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)
);
-
+
this.setState({
sortBy: {
index,
@@ -127,7 +126,7 @@ class KeyTable extends React.Component {
>
<a className="ds-font-size-sm">{_("What is an orphan key?")}</a>
</Tooltip>
- <Table
+ <Table
className="ds-margin-top"
aria-label="orph key table"
variant="compact"
@@ -135,7 +134,7 @@ class KeyTable extends React.Component {
<Thead>
<Tr>
{columns.map((column, idx) => (
- <Th
+ <Th
key={idx}
sort={column.sortable ? {
sortBy,
@@ -163,7 +162,7 @@ class KeyTable extends React.Component {
)}
{hasRows && (
<Td isActionCell>
- <ActionsColumn
+ <ActionsColumn
items={this.getActionsForRow(row)}
/>
</Td>
@@ -224,7 +223,7 @@ class CSRTable extends React.Component {
}
handleSort(_event, index, direction) {
- const sortedRows = [...this.state.rows].sort((a, b) =>
+ const sortedRows = [...this.state.rows].sort((a, b) =>
(a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)
);
this.setState({
@@ -316,7 +315,7 @@ class CSRTable extends React.Component {
onClear={(evt) => this.handleSearchChange(evt, '')}
/>
}
- <Table
+ <Table
className="ds-margin-top"
aria-label="csr table"
variant="compact"
@@ -324,7 +323,7 @@ class CSRTable extends React.Component {
<Thead>
<Tr>
{columns.map((column, idx) => (
- <Th
+ <Th
key={idx}
sort={column.sortable ? {
sortBy,
@@ -352,7 +351,7 @@ class CSRTable extends React.Component {
)}
{hasRows && (
<Td isActionCell>
- <ActionsColumn
+ <ActionsColumn
items={this.getActionsForRow(row)}
/>
</Td>
@@ -418,41 +417,11 @@ class CertTable extends React.Component {
}
handleSort(_event, columnIndex, direction) {
- const sorted_rows = [];
- const rows = [];
- let count = 0;
-
- // Convert the rows pairings into a sortable array
- for (let idx = 0; idx < this.state.rows.length; idx += 2) {
- sorted_rows.push({
- expandedRow: this.state.rows[idx + 1],
- 1: this.state.rows[idx].cells[0].content,
- 2: this.state.rows[idx].cells[1].content,
- 3: this.state.rows[idx].cells[2].content,
- issuer: this.state.rows[idx].issuer,
- flags: this.state.rows[idx].flags
- });
- }
+ const rows = [...this.state.rows];
- sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1);
+ rows.sort((a, b) => (a.cells[columnIndex].content > b.cells[columnIndex].content) ? 1 : -1);
if (direction !== SortByDirection.asc) {
- sorted_rows.reverse();
- }
-
- for (const srow of sorted_rows) {
- rows.push({
- isOpen: false,
- cells: [
- { content: srow[1] },
- { content: srow[2] },
- { content: srow[3] }
- ],
- issuer: srow.issuer,
- flags: srow.flags,
- });
- srow.expandedRow.parent = count;
- rows.push(srow.expandedRow);
- count += 2;
+ rows.reverse();
}
this.setState({
@@ -534,18 +503,16 @@ class CertTable extends React.Component {
rows.push(
{
isOpen: false,
- cells: [cert.attrs.nickname, cert.attrs.subject, cert.attrs.expires],
+ cells: [
+ { content: cert.attrs.nickname },
+ { content: cert.attrs.subject },
+ { content: cert.attrs.expires }
+ ],
issuer: cert.attrs.issuer,
flags: cert.attrs.flags,
-
- },
- {
- parent: count,
- fullWidth: true,
- cells: [{ title: this.getExpandedRow(cert.attrs.issuer, cert.attrs.flags) }]
- },
+ }
);
- count += 2;
+ count += 1;
}
this.setState({
@@ -587,7 +554,7 @@ class CertTable extends React.Component {
onChange={this.handleSearchChange}
onClear={(evt) => this.handleSearchChange(evt, '')}
/>}
- <Table
+ <Table
aria-label="cert table"
variant='compact'
>
@@ -626,7 +593,7 @@ class CertTable extends React.Component {
</Td>
))}
<Td isActionCell>
- <ActionsColumn
+ <ActionsColumn
items={this.getActionsForRow(row)}
/>
</Td>
@@ -646,7 +613,7 @@ class CertTable extends React.Component {
</Table>
{hasRows &&
<Pagination
- itemCount={this.state.rows.length / 2}
+ itemCount={this.state.rows.length}
widgetId="pagination-options-menu-bottom"
perPage={perPage}
page={page}
@@ -728,7 +695,7 @@ class CRLTable extends React.Component {
if (direction !== SortByDirection.asc) {
sorted_rows.reverse();
}
-
+
for (const srow of sorted_rows) {
rows.push({
isOpen: false,
@@ -806,14 +773,14 @@ class CRLTable extends React.Component {
onChange={this.handleSearchChange}
onClear={(evt) => this.handleSearchChange(evt, '')}
/>
- <Table
+ <Table
aria-label="CRL Table"
variant="compact"
>
<Thead>
<Tr>
{this.state.columns.map((column, idx) => (
- <Th
+ <Th
key={idx}
sort={column.sortable ? {
sortBy: this.state.sortBy,
@@ -836,7 +803,7 @@ class CRLTable extends React.Component {
))}
{this.state.hasRows && (
<Td isActionCell>
- <ActionsColumn
+ <ActionsColumn
items={this.getActionsForRow(row)}
/>
</Td>
--
2.52.0

View File

@ -1,538 +0,0 @@
From 09b6f770f15ee8d333377b7902e18b78f4f7008c Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Wed, 18 Feb 2026 09:26:57 +0100
Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for
parentid (#7264)
Description:
integerOrderingMatch was introduced as a requirement for parentid and
ancestorid indexes for performance reasons. But after #7096 the order
for parentid doesn't make a lot of difference.
Fix Description:
* Remove integerOrderingMatch requirement for parentid.
* Read only first 100 keys from dbscan in index ordering check
* Do not run dsctl index-check during RPM upgrade
Relates: https://github.com/389ds/389-ds-base/pull/7223
Reviewed by: @progier389, @tbordaz (Thanks!)
---
.../healthcheck/health_system_indexes_test.py | 83 ++++----------
ldap/servers/slapd/upgrade.c | 105 ------------------
rpm/389-ds-base.spec.in | 3 -
src/lib389/lib389/backend.py | 5 +-
src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++---------
5 files changed, 73 insertions(+), 222 deletions(-)
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
index 3b3651b38..71a9f590a 100644
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
@@ -180,7 +180,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
def test_missing_matching_rule(topology_st, log_buffering_enabled):
- """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch
+ """Check that healthcheck does NOT report DSBLE0007 when parentId index is missing integerOrderingMatch.
+ Both lexicographic and integer orderings are valid for parentid.
:id: 7ffa71db-8995-430a-bed8-59bce944221c
:setup: Standalone instance
@@ -190,19 +191,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
3. Use healthcheck without --json option
4. Use healthcheck with --json option
5. Re-add the matching rule
- 6. Use healthcheck without --json option
- 7. Use healthcheck with --json option
:expectedresults:
1. Success
2. Success
- 3. healthcheck reports DSBLE0007 code and related details
- 4. healthcheck reports DSBLE0007 code and related details
+ 3. healthcheck reports no issues found
+ 4. healthcheck reports no issues found
5. Success
- 6. healthcheck reports no issues found
- 7. healthcheck reports no issues found
"""
- RET_CODE = "DSBLE0007"
PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
standalone = topology_st.standalone
@@ -211,17 +207,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
parentid_index = Index(standalone, PARENTID_DN)
parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
log.info("Re-add the integerOrderingMatch matching rule")
parentid_index = Index(standalone, PARENTID_DN)
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
standalone.restart()
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
-
def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
"""Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
@@ -911,7 +904,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
def test_index_check_fixes_missing_matching_rule(topology_st):
- """Check if dsctl index-check --fix adds missing integerOrderingMatch
+ """Check that removing integerOrderingMatch from parentid config is not
+ flagged as an issue when disk ordering cannot be determined.
+ Both lexicographic and integer orderings are valid for parentid.
:id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
:setup: Standalone instance
@@ -919,18 +914,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
1. Create DS instance
2. Stop the server
3. Remove integerOrderingMatch from parentid index using DSEldif
- 4. Run dsctl index-check (should detect issue)
- 5. Run dsctl index-check --fix
- 6. Verify integerOrderingMatch was added back
- 7. Start the server
+ 4. Run dsctl index-check (should NOT detect issue since disk ordering is unknown)
+ 5. Start the server
:expectedresults:
1. Success
2. Success
3. Success
- 4. index-check returns False and detects missing matching rule
- 5. index-check returns True after fix
- 6. integerOrderingMatch is present
- 7. Success
+ 4. index-check returns True (no issues, disk ordering unknown)
+ 5. Success
"""
from lib389.cli_ctl.dbtasks import dbtasks_index_check
from lib389.dseldif import DSEldif
@@ -964,34 +955,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
f"integerOrderingMatch should be removed, but found: {mr}"
log.info("integerOrderingMatch removed from parentid index")
- log.info("Run index-check without --fix (should detect issue)")
+ log.info("Run index-check (should NOT detect issue - disk ordering unknown)")
args = FakeArgs()
args.backend = "userRoot"
args.fix = False
result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
- assert result is False, "index-check should detect missing matching rule"
- assert topology_st.logcap.contains("missing integerOrderingMatch")
+ assert result is True, \
+ "index-check should not flag missing integerOrderingMatch when disk ordering is unknown"
+ assert topology_st.logcap.contains("could not determine disk ordering")
topology_st.logcap.flush()
- log.info("Run index-check with --fix")
- args.fix = True
- result = dbtasks_index_check(standalone, topology_st.logcap.log, args)
- assert result is True, "index-check --fix should succeed"
- assert topology_st.logcap.contains("integerOrderingMatch")
- topology_st.logcap.flush()
-
- log.info("Verify integerOrderingMatch was added back")
- dse_ldif = DSEldif(standalone) # Reload to get fresh data
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
- assert matching_rules is not None, "nsMatchingRule should be present"
- found_int_order = False
- for mr in matching_rules:
- if "integerorderingmatch" in mr.lower():
- found_int_order = True
- break
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
- log.info("integerOrderingMatch successfully added back")
+ log.info("Restore integerOrderingMatch and start the server")
+ dse_ldif = DSEldif(standalone)
+ dse_ldif.add(parentid_dn, "nsMatchingRule", "integerOrderingMatch")
log.info("Start the server")
standalone.start()
@@ -1081,7 +1058,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
:steps:
1. Create DS instance
2. Stop the server
- 3. Add multiple issues: scanlimit, ancestorid config, missing matching rule
+ 3. Add multiple issues: scanlimit and ancestorid config
4. Run dsctl index-check (should detect all issues)
5. Run dsctl index-check --fix
6. Verify all issues were fixed
@@ -1123,14 +1100,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
]
dse_ldif.add_entry(ancestorid_entry)
- log.info("Add issue 3: Remove integerOrderingMatch from parentid")
- dse_ldif = DSEldif(standalone) # Reload
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
- if matching_rules:
- for mr in matching_rules:
- if "integerorderingmatch" in mr.lower():
- dse_ldif.delete(parentid_dn, "nsMatchingRule", mr)
-
log.info("Run index-check without --fix (should detect all issues)")
args = FakeArgs()
args.backend = "userRoot"
@@ -1161,16 +1130,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
- # Check matching rule added back
- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule")
- found_int_order = False
- if matching_rules:
- for mr in matching_rules:
- if "integerorderingmatch" in mr.lower():
- found_int_order = True
- break
- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}"
-
log.info("All issues verified as fixed")
log.info("Run index-check again to confirm all clear")
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
index d9156cae9..537c38feb 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -500,107 +500,6 @@ upgrade_remove_ancestorid_index_config(void)
return uresult;
}
-/*
- * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
- * matching rule.
- *
- * This function logs a warning if we detect this condition, advising
- * the administrator to reindex the affected attributes.
- */
-static upgrade_status
-upgrade_check_id_index_matching_rule(void)
-{
- struct slapi_pblock *pb = slapi_pblock_new();
- Slapi_Entry **backends = NULL;
- const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
- const char *be_filter = "(objectclass=nsBackendInstance)";
- const char *attrs_to_check[] = {"parentid", NULL};
- upgrade_status uresult = UPGRADE_SUCCESS;
-
- /* Search for all backend instances */
- slapi_search_internal_set_pb(
- pb, be_base_dn,
- LDAP_SCOPE_ONELEVEL,
- be_filter, NULL, 0, NULL, NULL,
- plugin_get_default_component_id(), 0);
- slapi_search_internal_pb(pb);
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
-
- if (backends) {
- for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
- const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]);
- const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
- if (!be_dn || !be_name) {
- continue;
- }
-
- /* Check each attribute that should have integerOrderingMatch */
- for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
- const char *attr_name = attrs_to_check[attr_idx];
- struct slapi_pblock *idx_pb = slapi_pblock_new();
- Slapi_Entry **idx_entries = NULL;
- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s",
- attr_name, be_dn);
- char *idx_filter = "(objectclass=nsIndex)";
- PRBool has_matching_rule = PR_FALSE;
-
- if (!idx_dn) {
- slapi_pblock_destroy(idx_pb);
- continue;
- }
-
- slapi_search_internal_set_pb(
- idx_pb, idx_dn,
- LDAP_SCOPE_BASE,
- idx_filter, NULL, 0, NULL, NULL,
- plugin_get_default_component_id(), 0);
- slapi_search_internal_pb(idx_pb);
- slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
-
- if (idx_entries && idx_entries[0]) {
- /* Index exists, check if it has integerOrderingMatch */
- Slapi_Attr *mr_attr = NULL;
- if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
- Slapi_Value *sval = NULL;
- int idx;
- for (idx = slapi_attr_first_value(mr_attr, &sval);
- idx != -1;
- idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
- const struct berval *bval = slapi_value_get_berval(sval);
- if (bval && bval->bv_val &&
- strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
- has_matching_rule = PR_TRUE;
- break;
- }
- }
- }
-
- if (!has_matching_rule) {
- /* Index exists but doesn't have integerOrderingMatch, log a warning */
- slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
- "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
- "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
- "To fix this, add the matching rule and reindex: "
- "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
- "dsconf <instance> backend index reindex --attr %s %s. "
- "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
- "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
- attr_name, be_name, attr_name, be_name, attr_name, be_name);
- }
- }
-
- slapi_ch_free_string(&idx_dn);
- slapi_free_search_results_internal(idx_pb);
- slapi_pblock_destroy(idx_pb);
- }
- }
- }
-
- slapi_free_search_results_internal(pb);
- slapi_pblock_destroy(pb);
-
- return uresult;
-}
upgrade_status
upgrade_server(void)
@@ -637,10 +536,6 @@ upgrade_server(void)
return UPGRADE_FAILURE;
}
- if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
- return UPGRADE_FAILURE;
- }
-
return UPGRADE_SUCCESS;
}
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 0175dfa7c..f78258f6a 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -667,9 +667,6 @@ for dir in "$instbase"/slapd-* ; do
else
echo "instance $inst is not running" >> "$output" 2>&1 || :
fi
- # Run index-check on all instances (running or not)
- # This fixes index ordering mismatches from older versions
- dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
ninst=$((ninst + 1))
done
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index 1d9be4683..29a1796ac 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -617,9 +617,10 @@ class Backend(DSLdapObject):
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
# Note: entryrdn and ancestorid are internal system indexes that are not
# exposed in cn=config - they are managed internally by the server.
- # Only parentid has a DSE config entry (for the integerOrderingMatch rule).
+ # parentid works correctly with both lexicographic and integer ordering,
+ # so integerOrderingMatch is not required.
expected_system_indexes = {
- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'},
+ 'parentid': {'types': ['eq'], 'matching_rule': None},
'objectClass': {'types': ['eq'], 'matching_rule': None},
'aci': {'types': ['pres'], 'matching_rule': None},
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
index cd96cdaf7..b02de203f 100644
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
@@ -10,6 +10,7 @@
import glob
import os
import re
+import signal
import subprocess
from enum import Enum
from lib389._constants import TaskWarning
@@ -263,45 +264,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
if not index_file:
return IndexOrdering.UNKNOWN
+ # Only read the first 100 lines from dbscan to avoid scanning the
+ # entire index (which can take hours on large databases).
try:
- result = subprocess.run(
+ proc = subprocess.Popen(
[dbscan_path, "-f", index_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
- timeout=60,
)
- if result.returncode != 0:
- log.warning(" dbscan returned non-zero exit code for %s", index_file)
- return IndexOrdering.UNKNOWN
-
- # Parse keys from dbscan output
keys = []
- for line in result.stdout.split("\n"):
+ line_count = 0
+ assert proc.stdout is not None
+ for line in proc.stdout:
+ line_count += 1
+ if line_count > 100:
+ break
line = line.strip()
if line.startswith("="):
match = re.match(r"^=(\d+)", line)
if match:
keys.append(int(match.group(1)))
+ proc.terminate()
+ try:
+ proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ proc.wait()
+
+ if proc.returncode not in (0, -signal.SIGTERM):
+ log.warning(" dbscan returned non-zero exit code for %s", index_file)
+ return IndexOrdering.UNKNOWN
+
if len(keys) < 2:
return IndexOrdering.UNKNOWN
# Check if keys are in integer order by looking for decreasing numeric values
# (which would indicate lexicographic ordering, e.g., "3" < "30" < "4")
prev_id = keys[0]
- for i in range(1, min(len(keys), 100)):
- current_id = keys[i]
+ for current_id in keys[1:]:
if prev_id > current_id:
return IndexOrdering.LEXICOGRAPHIC
prev_id = current_id
return IndexOrdering.INTEGER
- except subprocess.TimeoutExpired:
- log.warning(" dbscan timed out for %s", index_file)
- return IndexOrdering.UNKNOWN
except OSError as e:
log.warning(" Error running dbscan: %s", e)
return IndexOrdering.UNKNOWN
@@ -375,8 +384,7 @@ def dbtasks_index_check(inst, log, args):
# Track all issues found
all_ok = True
- mismatches = [] # (backend, index_name) tuples needing reindex
- missing_matching_rules = [] # (backend, index_name) tuples missing integerOrderingMatch
+ config_fixes = [] # (backend, index_name, action) tuples: action is "add_mr" or "remove_mr"
scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit
ancestorid_configs_to_remove = [] # backend names with ancestorid config entries
remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes
@@ -409,13 +417,6 @@ def dbtasks_index_check(inst, log, args):
if disk_ordering == IndexOrdering.UNKNOWN:
log.info(" %s - could not determine disk ordering, skipping", index_name)
- # For parentid, still check if matching rule is missing
- if index_name == "parentid":
- config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
- if not config_has_int_order:
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
- missing_matching_rules.append((backend, index_name))
- all_ok = False
continue
config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
@@ -423,18 +424,15 @@ def dbtasks_index_check(inst, log, args):
log.info(" %s - config: %s, disk: %s",
index_name, config_desc, disk_ordering.value)
- # For parentid, the desired state is always integer ordering
+ # Both orderings are valid for parentid, but config must match disk.
if index_name == "parentid":
- if not config_has_int_order:
- log.warning(" %s - missing integerOrderingMatch in config", index_name)
- if (backend, index_name) not in missing_matching_rules:
- missing_matching_rules.append((backend, index_name))
+ if config_has_int_order and disk_ordering == IndexOrdering.LEXICOGRAPHIC:
+ log.warning(" %s - MISMATCH: config has integerOrderingMatch but disk is lexicographic", index_name)
+ config_fixes.append((backend, index_name, "remove_mr"))
all_ok = False
-
- if disk_ordering == IndexOrdering.LEXICOGRAPHIC:
- log.warning(" %s - disk ordering is lexicographic, needs reindex", index_name)
- if (backend, index_name) not in mismatches:
- mismatches.append((backend, index_name))
+ elif not config_has_int_order and disk_ordering == IndexOrdering.INTEGER:
+ log.warning(" %s - MISMATCH: config is lexicographic but disk has integer ordering", index_name)
+ config_fixes.append((backend, index_name, "add_mr"))
all_ok = False
# Handle issues
@@ -480,26 +478,27 @@ def dbtasks_index_check(inst, log, args):
log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e)
return False
- # Add missing matching rules to dse.ldif
- for backend, index_name in missing_matching_rules:
+ # Fix config-vs-disk ordering mismatches by adjusting config to match disk
+ for backend, index_name, action in config_fixes:
index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(
index_name, backend
)
- log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
- try:
- dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
- log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
- except Exception as e:
- log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
- return False
-
- # Reindex indexes with disk ordering issues
- for backend, index_name in mismatches:
- log.info(" Reindexing %s in backend %s...", index_name, backend)
- if not inst.db2index(bename=backend, attrs=[index_name]):
- log.error(" Failed to reindex %s", index_name)
- return False
- log.info(" Reindex of %s completed successfully", index_name)
+ if action == "add_mr":
+ log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend)
+ try:
+ dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch")
+ log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name)
+ except Exception as e:
+ log.error(" Failed to update dse.ldif for %s: %s", index_name, e)
+ return False
+ elif action == "remove_mr":
+ log.info(" Removing integerOrderingMatch from %s in backend %s...", index_name, backend)
+ try:
+ dse_ldif.delete(index_dn, "nsMatchingRule", "integerOrderingMatch")
+ log.info(" Removed integerOrderingMatch from %s", index_name)
+ except Exception as e:
+ log.error(" Failed to remove integerOrderingMatch from %s: %s", index_name, e)
+ return False
log.info("All issues fixed")
return True
@@ -563,5 +562,5 @@ def create_parser(subcommands):
index_check_parser.add_argument('backend', nargs='?', default=None,
help="Backend to check. If not specified, all backends are checked.")
index_check_parser.add_argument('--fix', action='store_true', default=False,
- help="Fix mismatches by reindexing affected indexes")
+ help="Fix mismatches by adjusting config to match on-disk data")
index_check_parser.set_defaults(func=dbtasks_index_check)
--
2.52.0

View File

@ -1,202 +0,0 @@
From 31bef3852f7064c73f66974d7c41262d2a724eab Mon Sep 17 00:00:00 2001
From: Alex Kulberg <vectinx@yandex.ru>
Date: Tue, 9 Dec 2025 17:11:56 +0300
Subject: [PATCH] Issue 7053 - Remove memberof_del_dn_from_groups from MemberOf
plugin (#7064)
Bug Description:
The member plugin creates redundant changes to the member attribute
in groups when deleting a user, although the referential integrity
of the member attribute should be controlled by the Referential Integrity plugin.
Furthermore, memberof doesn't take replication of operations into account
and performs the change on every server instance in the topology.
Fix Description:
Remove the `memberof_del_dn_from_groups` function from the MemberOf plugin,
completely transferring responsibility for deleting users from groups
to the Referential Integrity plugin.
Relates: https://github.com/389ds/389-ds-base/issues/7053
Reviewed by: @tbordaz
---
.../memberof_include_scopes_test.py | 13 ++-
ldap/servers/plugins/memberof/memberof.c | 79 -------------------
2 files changed, 9 insertions(+), 83 deletions(-)
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
index e1d3b0a96..347eb880f 100644
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
@@ -13,7 +13,7 @@ import time
from lib389.utils import ensure_str
from lib389.topologies import topology_st as topo
from lib389._constants import *
-from lib389.plugins import MemberOfPlugin
+from lib389.plugins import MemberOfPlugin, ReferentialIntegrityPlugin
from lib389.idm.user import UserAccount, UserAccounts
from lib389.idm.group import Group, Groups
from lib389.idm.nscontainer import nsContainers
@@ -77,6 +77,13 @@ def test_multiple_scopes(topo):
inst = topo.standalone
+ EXCLUDED_SUBTREE = 'cn=exclude,%s' % SUFFIX
+ # enable Referential Integrity plugin
+ # to correctly process the 'member' attribute
+ refint = ReferentialIntegrityPlugin(inst)
+ refint.add_excludescope(EXCLUDED_SUBTREE)
+ refint.enable()
+
# configure plugin
memberof = MemberOfPlugin(inst)
memberof.enable()
@@ -106,7 +113,6 @@ def test_multiple_scopes(topo):
check_membership(inst, f'uid=test_m3,{SUBTREE_3}', f'cn=g3,{SUBTREE_3}', False)
# Set exclude scope
- EXCLUDED_SUBTREE = 'cn=exclude,%s' % SUFFIX
EXCLUDED_USER = f"uid=test_m1,{EXCLUDED_SUBTREE}"
INCLUDED_USER = f"uid=test_m1,{SUBTREE_1}"
GROUP_DN = f'cn=g1,{SUBTREE_1}'
@@ -122,9 +128,8 @@ def test_multiple_scopes(topo):
# Check memberOf and group are cleaned up
check_membership(inst, EXCLUDED_USER, GROUP_DN, False)
group = Group(topo.standalone, dn=GROUP_DN)
- assert not group.present("member", EXCLUDED_USER)
assert not group.present("member", INCLUDED_USER)
-
+ assert not group.present("member", EXCLUDED_USER)
if __name__ == '__main__':
# Run isolated
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
index c4b5afee7..5de2e9e72 100644
--- a/ldap/servers/plugins/memberof/memberof.c
+++ b/ldap/servers/plugins/memberof/memberof.c
@@ -151,7 +151,6 @@ static void memberof_set_plugin_id(void *plugin_id);
static int memberof_compare(MemberOfConfig *config, const void *a, const void *b);
static int memberof_qsort_compare(const void *a, const void *b);
static void memberof_load_array(Slapi_Value **array, Slapi_Attr *attr);
-static int memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_Entry *e, Slapi_DN *sdn);
static int memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_DN *sdn, MemberOfConfig *config, char **types, plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache);
static int memberof_is_direct_member(MemberOfConfig *config, Slapi_Value *groupdn, Slapi_Value *memberdn);
static int memberof_is_grouping_attr(char *type, MemberOfConfig *config);
@@ -540,21 +539,6 @@ deferred_modrdn_func(MemberofDeferredModrdnTask *task)
* attributes to refer to the new name. */
if (ret == LDAP_SUCCESS && pre_sdn && post_sdn) {
if (!memberof_entry_in_scope(&configCopy, &post_entry_info)) {
- /*
- * After modrdn the group contains both the pre and post DN's as
- * members, so we need to cleanup both in this case.
- */
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, pre_e, pre_sdn))) {
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
- "deferred_modrdn_func - Delete dn failed for preop entry(%s), error (%d)\n",
- slapi_sdn_get_dn(pre_sdn), ret);
- }
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, post_e, post_sdn))) {
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
- "deferred_modrdn_func - Delete dn failed for postop entry(%s), error (%d)\n",
- slapi_sdn_get_dn(post_sdn), ret);
- }
-
if (ret == LDAP_SUCCESS && pre_e && configCopy.group_filter &&
0 == slapi_filter_test_simple(pre_e, configCopy.group_filter))
{
@@ -638,16 +622,6 @@ deferred_del_func(MemberofDeferredDelTask *task)
free_configCopy = PR_TRUE;
memberof_unlock_config();
- /* remove this DN from the
- * membership lists of groups
- */
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, e, sdn))) {
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
- "deferred_del_func - Error deleting dn (%s) from group. Error (%d)\n",
- slapi_sdn_get_dn(sdn), ret);
- goto bail;
- }
-
/* is the entry of interest as a group? */
if (e && configCopy.group_filter && 0 == slapi_filter_test_simple(e, configCopy.group_filter)) {
Slapi_Attr *attr = 0;
@@ -1456,16 +1430,6 @@ memberof_postop_del(Slapi_PBlock *pb)
memberof_copy_config(&configCopy, memberof_get_config());
memberof_unlock_config();
- /* remove this DN from the
- * membership lists of groups
- */
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, e, sdn))) {
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
- "memberof_postop_del - Error deleting dn (%s) from group. Error (%d)\n",
- slapi_sdn_get_dn(sdn), ret);
- goto bail;
- }
-
/* is the entry of interest as a group? */
if (e && configCopy.group_filter && 0 == slapi_filter_test_simple(e, configCopy.group_filter)) {
Slapi_Attr *attr = 0;
@@ -1496,34 +1460,6 @@ done:
}
-/* Deletes a member dn from all groups that refer to it. */
-static int
-memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_Entry *e, Slapi_DN *sdn)
-{
- char *groupattrs[2] = {0, 0};
- int rc = LDAP_SUCCESS;
- int cached = 0;
-
- /* Loop through each grouping attribute to find groups that have
- * dn as a member. For any matches, delete the dn value from the
- * same grouping attribute. */
- for (size_t i = 0; config->groupattrs && config->groupattrs[i] && rc == LDAP_SUCCESS; i++) {
- memberof_del_dn_data data = {(char *)slapi_sdn_get_dn(sdn),
- config->groupattrs[i], config};
-
- groupattrs[0] = config->groupattrs[i];
-
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
- "memberof_del_dn_from_groups: Ancestors of %s attr: %s\n",
- slapi_sdn_get_dn(sdn),
- groupattrs[0]);
- rc = memberof_call_foreach_dn(pb, e, sdn, config, groupattrs,
- memberof_del_dn_type_callback, &data, &cached, PR_FALSE);
- }
-
- return rc;
-}
-
int
memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
{
@@ -1904,21 +1840,6 @@ memberof_postop_modrdn(Slapi_PBlock *pb)
* attributes to refer to the new name. */
if (ret == LDAP_SUCCESS && pre_sdn && post_sdn) {
if (!memberof_entry_in_scope(&configCopy, &post_entry_info)) {
- /*
- * After modrdn the group contains both the pre and post DN's as
- * members, so we need to cleanup both in this case.
- */
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, pre_e, pre_sdn))) {
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
- "memberof_postop_modrdn - Delete dn failed for preop entry(%s), error (%d)\n",
- slapi_sdn_get_dn(pre_sdn), ret);
- }
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, post_e, post_sdn))) {
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
- "memberof_postop_modrdn - Delete dn failed for postop entry(%s), error (%d)\n",
- slapi_sdn_get_dn(post_sdn), ret);
- }
-
if (ret == LDAP_SUCCESS && pre_e && configCopy.group_filter &&
0 == slapi_filter_test_simple(pre_e, configCopy.group_filter)) {
/* is the entry of interest as a group? */
--
2.52.0

View File

@ -1,952 +0,0 @@
From 0c5f7b9909ababc53e73e9469da6e30b81637cd0 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 23 Feb 2026 09:49:52 +0100
Subject: [PATCH] Issue 5853 - Update concread to 0.5.10
Description:
Update concread to 0.5.10 and update Cargo.lock
Relates: https://github.com/389ds/389-ds-base/issues/5853
Reviewed by: @droideck (Thanks!)
---
src/Cargo.lock | 515 +++++++++++++++++++++++----------------
src/librslapd/Cargo.toml | 2 +-
2 files changed, 312 insertions(+), 205 deletions(-)
diff --git a/src/Cargo.lock b/src/Cargo.lock
index 87aeee852..425371478 100644
--- a/src/Cargo.lock
+++ b/src/Cargo.lock
@@ -2,27 +2,18 @@
# It is not intended for manual editing.
version = 3
-[[package]]
-name = "addr2line"
-version = "0.24.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
[[package]]
name = "atty"
version = "0.2.14"
@@ -40,21 +31,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-[[package]]
-name = "backtrace"
-version = "0.3.75"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-targets",
-]
-
[[package]]
name = "base64"
version = "0.13.1"
@@ -69,9 +45,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.9.1"
+version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "byteorder"
@@ -86,8 +62,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
dependencies = [
"clap",
- "heck",
- "indexmap",
+ "heck 0.4.1",
+ "indexmap 1.9.3",
"log",
"proc-macro2",
"quote",
@@ -100,10 +76,11 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.27"
+version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
+checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
+ "find-msvc-tools",
"jobserver",
"libc",
"shlex",
@@ -111,9 +88,9 @@ dependencies = [
[[package]]
name = "cfg-if"
-version = "1.0.1"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
@@ -124,7 +101,7 @@ dependencies = [
"atty",
"bitflags 1.3.2",
"clap_lex",
- "indexmap",
+ "indexmap 1.9.3",
"strsim",
"termcolor",
"textwrap",
@@ -141,14 +118,14 @@ dependencies = [
[[package]]
name = "concread"
-version = "0.5.6"
+version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b639eeaa550eba0c8be45b292d5e272e6d29bfdffb4df6925d651ed9ed10fd6"
+checksum = "6588e9e68e11207fb9a5aabd88765187969e6bcba98763c40bcad87b2a73e9f5"
dependencies = [
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
- "foldhash",
+ "foldhash 0.2.0",
"lru",
"smallvec",
"sptr",
@@ -210,9 +187,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
-version = "0.3.12"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
@@ -232,17 +209,29 @@ checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f"
dependencies = [
"base64",
"byteorder",
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
"openssl",
"zeroize",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -260,32 +249,39 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "getrandom"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
]
[[package]]
name = "getrandom"
-version = "0.3.3"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
- "wasi 0.14.2+wasi-0.2.4",
+ "wasip2",
]
[[package]]
-name = "gimli"
-version = "0.31.1"
+name = "getrandom"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasip3",
+]
[[package]]
name = "hashbrown"
@@ -295,13 +291,22 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
-version = "0.15.4"
+version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash",
+ "foldhash 0.2.0",
]
[[package]]
@@ -310,6 +315,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
[[package]]
name = "hermit-abi"
version = "0.1.19"
@@ -319,6 +330,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -329,27 +346,45 @@ dependencies = [
"hashbrown 0.12.3",
]
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
+
[[package]]
name = "itoa"
-version = "1.0.15"
+version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jobserver"
-version = "0.1.33"
+version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
- "getrandom 0.3.3",
+ "getrandom 0.3.4",
"libc",
]
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
[[package]]
name = "libc"
-version = "0.2.174"
+version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "librnsslapd"
@@ -372,48 +407,30 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.9.4"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
-version = "0.4.27"
+version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru"
-version = "0.13.0"
+version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
- "hashbrown 0.15.4",
+ "hashbrown 0.16.1",
]
[[package]]
name = "memchr"
-version = "2.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
-
-[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
-]
-
-[[package]]
-name = "object"
-version = "0.36.7"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
-dependencies = [
- "memchr",
-]
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell"
@@ -423,11 +440,11 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl"
-version = "0.10.73"
+version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"cfg-if",
"foreign-types",
"libc",
@@ -444,14 +461,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
name = "openssl-sys"
-version = "0.9.109"
+version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [
"cc",
"libc",
@@ -496,6 +513,16 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.117",
+]
+
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@@ -504,9 +531,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
-version = "1.0.95"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -526,9 +553,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.40"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
@@ -539,19 +566,13 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-[[package]]
-name = "rustc-demangle"
-version = "0.1.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
-
[[package]]
name = "rustix"
-version = "1.0.7"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys",
@@ -559,41 +580,52 @@ dependencies = [
]
[[package]]
-name = "ryu"
-version = "1.0.20"
+name = "semver"
+version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
-version = "1.0.219"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
name = "serde_json"
-version = "1.0.140"
+version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
- "ryu",
"serde",
+ "serde_core",
+ "zmij",
]
[[package]]
@@ -649,9 +681,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.103"
+version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -660,12 +692,12 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.20.0"
+version = "3.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
dependencies = [
"fastrand",
- "getrandom 0.3.3",
+ "getrandom 0.4.1",
"once_cell",
"rustix",
"windows-sys",
@@ -688,11 +720,10 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
[[package]]
name = "tokio"
-version = "1.45.1"
+version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
- "backtrace",
"pin-project-lite",
]
@@ -707,9 +738,9 @@ dependencies = [
[[package]]
name = "tracing"
-version = "0.1.41"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@@ -718,29 +749,35 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.30"
+version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
name = "tracing-core"
-version = "0.1.34"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "unicode-ident"
-version = "1.0.18"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "uuid"
@@ -748,7 +785,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
]
[[package]]
@@ -764,12 +801,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
-name = "wasi"
-version = "0.14.2+wasi-0.2.4"
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap 2.13.0",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
- "wit-bindgen-rt",
+ "bitflags 2.11.0",
+ "hashbrown 0.15.5",
+ "indexmap 2.13.0",
+ "semver",
]
[[package]]
@@ -790,9 +870,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.9"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
@@ -804,103 +884,130 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
-name = "windows-sys"
-version = "0.59.0"
+name = "windows-link"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets",
-]
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
-name = "windows-targets"
-version = "0.52.6"
+name = "windows-sys"
+version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows-link",
]
[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
+name = "wit-bindgen"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
+name = "wit-bindgen-core"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "wit-parser",
+]
[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.6"
+name = "wit-bindgen-rust"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "indexmap 2.13.0",
+ "prettyplease",
+ "syn 2.0.117",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.6"
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
+name = "wit-component"
+version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.11.0",
+ "indexmap 2.13.0",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
[[package]]
-name = "wit-bindgen-rt"
-version = "0.39.0"
+name = "wit-parser"
+version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
- "bitflags 2.9.1",
+ "anyhow",
+ "id-arena",
+ "indexmap 2.13.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
]
[[package]]
name = "zeroize"
-version = "1.8.1"
+version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
-version = "1.4.2"
+version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/src/librslapd/Cargo.toml b/src/librslapd/Cargo.toml
index f38f93f4c..61100f381 100644
--- a/src/librslapd/Cargo.toml
+++ b/src/librslapd/Cargo.toml
@@ -16,7 +16,7 @@ crate-type = ["staticlib", "lib"]
[dependencies]
slapd = { path = "../slapd" }
libc = "0.2"
-concread = "0.5.6"
+concread = "0.5.10"
[build-dependencies]
cbindgen = "0.26"
--
2.53.0

View File

@ -1,86 +0,0 @@
From a3fd3897ede4166625194d48b010b6dbdcdf9f70 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 23 Feb 2026 12:37:26 -0500
Subject: [PATCH] Issue 7271 - plugins that create threads need to update
active thread count
Description:
Plugins that create threads need to up to the global active thread count.
Otherwise when the server is being stopped the plugin's close function gets
called while these threads are still running and still using the plugin
configuration. This can lead to crashes.
relates: https://github.com/389ds/389-ds-base/issues/7271
Reviewed by: progier & tbordaz (Thanks!!)
---
ldap/servers/plugins/replication/repl5_protocol.c | 5 +++++
ldap/servers/plugins/retrocl/retrocl_trim.c | 7 ++++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c
index 5d4a0e455..bc9580319 100644
--- a/ldap/servers/plugins/replication/repl5_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_protocol.c
@@ -23,6 +23,7 @@
#include "repl5.h"
#include "repl5_prot_private.h"
+#include "slap.h"
#define PROTOCOL_5_INCREMENTAL 1
#define PROTOCOL_5_TOTAL 2
@@ -237,6 +238,8 @@ prot_thread_main(void *arg)
return;
}
+ g_incr_active_threadcnt();
+
set_thread_private_agmtname(agmt_get_long_name(agmt));
done = 0;
@@ -301,6 +304,8 @@ prot_thread_main(void *arg)
done = 1;
}
}
+
+ g_decr_active_threadcnt();
}
/*
diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c
index 8fcd3d32b..8fdc75c62 100644
--- a/ldap/servers/plugins/retrocl/retrocl_trim.c
+++ b/ldap/servers/plugins/retrocl/retrocl_trim.c
@@ -243,6 +243,8 @@ trim_changelog(void)
now_interval = slapi_current_rel_time_t(); /* monotonic time for interval */
+ g_incr_active_threadcnt();
+
PR_Lock(ts.ts_s_trim_mutex);
max_age = ts.ts_c_max_age;
trim_interval = ts.ts_c_trim_interval;
@@ -257,7 +259,7 @@ trim_changelog(void)
*/
done = 0;
now_maxage = slapi_current_utc_time(); /* real time for trim candidates */
- while (!done && retrocl_trimming == 1) {
+ while (!done && retrocl_trimming == 1 && !slapi_is_shutting_down()) {
int did_delete;
did_delete = 0;
@@ -309,6 +311,9 @@ trim_changelog(void)
"trim_changelog: removed %d change records\n",
num_deleted);
}
+
+ g_decr_active_threadcnt();
+
return rc;
}
--
2.53.0

View File

@ -1,93 +0,0 @@
From f4817944e43e080b1ab15c3fa13359252429e500 Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Wed, 25 Feb 2026 14:06:42 +0100
Subject: [PATCH] Security fix for CVE-2025-14905
Description:
A vulnerability was found in the 389 Directory Server.
The 389 Directory Server present a risk of heap buffer overflow that
can be exploited to excute a Denial of Service and potential Remote
Code Execution
References:
- https://access.redhat.com/security/cve/CVE-2025-14905
- https://bugzilla.redhat.com/show_bug.cgi?id=2423624
---
ldap/servers/slapd/schema.c | 47 ++++++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
index 9ef4ee4bf..7712a720d 100644
--- a/ldap/servers/slapd/schema.c
+++ b/ldap/servers/slapd/schema.c
@@ -1410,6 +1410,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
const char *attr_desc, *syntaxoid;
char *outp, syntaxlengthbuf[128];
int i;
+ int nb_aliases = 0;
vals[0] = &val;
@@ -1435,6 +1436,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
if (asip->asi_aliases != NULL) {
for (i = 0; asip->asi_aliases[i] != NULL; ++i) {
aliaslen += strlen(asip->asi_aliases[i]);
+ nb_aliases++;
}
}
@@ -1452,15 +1454,42 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
* XXX: 256 is a magic number... it must be big enough to account for
* all of the fixed sized items we output.
*/
- sizedbuffer_allocate(aew->psbAttrTypes, 256 + strlen(asip->asi_oid) +
- strlen(asip->asi_name) +
- aliaslen + strlen_null_ok(attr_desc) +
- strlen(syntaxoid) +
- strlen_null_ok(asip->asi_superior) +
- strlen_null_ok(asip->asi_mr_equality) +
- strlen_null_ok(asip->asi_mr_ordering) +
- strlen_null_ok(asip->asi_mr_substring) +
- strcat_extensions(NULL, asip->asi_extensions));
+ {
+ int asi_oid_strlen = strlen(asip->asi_oid) + 8; /* "( %s NAME " */
+ int asi_name_strlen = strlen(asip->asi_name) + 6; /* "( '%s' ...)" */
+ int asi_aliases_strlen = aliaslen + nb_aliases * 3; /* "'%s' " */
+ int asi_desc_strlen = strlen_null_ok(attr_desc) + 7; /* "DESC '%s'" */
+ int asi_syntaxoid_strlen = strlen("SYNTAX ") + strlen(syntaxoid) + strlen(syntaxlengthbuf);
+ int asi_superior_strlen = strlen("SUP ") + strlen_null_ok(asip->asi_superior);
+ int asi_mr_equality_strlen = strlen("EQUALITY ") + strlen_null_ok(asip->asi_mr_equality);
+ int asi_mr_ordering_strlen = strlen("ORDERING ") + strlen_null_ok(asip->asi_mr_ordering);
+ int asi_mr_substring_strlen = strlen("SUBSTR ") + strlen_null_ok(asip->asi_mr_substring);
+ int asi_flags_strlen = strlen("SINGLE-VALUE ") +
+ strlen(schema_obsolete_with_spaces) +
+ strlen(schema_collective_with_spaces) +
+ strlen(schema_nousermod_with_spaces) +
+ strlen("USAGE distributedOperation ") +
+ strlen("USAGE dSAOperation ") +
+ strlen("USAGE directoryOperation ");
+ int asi_extension_strlen = strcat_extensions(NULL, asip->asi_extensions);
+
+ if (aew->enquote_sup_oc) {
+ /* it enquote the syntax oid */
+ asi_syntaxoid_strlen += 2;
+ }
+
+ sizedbuffer_allocate(aew->psbAttrTypes, 256 + asi_oid_strlen +
+ asi_name_strlen +
+ asi_aliases_strlen +
+ asi_desc_strlen +
+ asi_syntaxoid_strlen +
+ asi_superior_strlen +
+ asi_mr_equality_strlen +
+ asi_mr_ordering_strlen +
+ asi_mr_substring_strlen +
+ asi_extension_strlen +
+ asi_flags_strlen);
+ }
/*
* Overall strategy is to maintain a pointer to the next location in
--
2.53.0

View File

@ -1,262 +0,0 @@
From c6bb96c95dcbff0052fc2decab7020b0f833f313 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 2 Mar 2026 14:31:29 -0500
Subject: [PATCH] Issue 7271 - implement a pre-close plugin function
Description:
replication protocol could benefit from being notifioed the shutdown process
has started before calling the "close" plugin function. This would allow the
replication protocol to wake up and adjust its active thread count to prevent
a hang during shutdown.
relates: https://github.com/389ds/389-ds-base/issues/7271
Reviewed by: progier, spichugi, and vashirov(Thanks!!!)
---
ldap/servers/plugins/replication/repl5_init.c | 19 ++++++++----
ldap/servers/slapd/daemon.c | 6 +++-
ldap/servers/slapd/pblock.c | 30 +++++++++++++------
ldap/servers/slapd/plugin.c | 24 ++++++++++++++-
ldap/servers/slapd/proto-slap.h | 3 +-
ldap/servers/slapd/slap.h | 3 +-
ldap/servers/slapd/slapi-plugin.h | 3 +-
7 files changed, 69 insertions(+), 19 deletions(-)
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
index 5047fb8dc..395744db8 100644
--- a/ldap/servers/plugins/replication/repl5_init.c
+++ b/ldap/servers/plugins/replication/repl5_init.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -122,6 +122,8 @@ static PRUintn thread_private_agmtname; /* thread private index for logging*/
static PRUintn thread_private_cache;
static PRUintn thread_primary_csn;
+static int multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)));
+
char *
get_thread_private_agmtname()
{
@@ -836,10 +838,6 @@ multisupplier_stop(Slapi_PBlock *pb __attribute__((unused)))
int rc = 0; /* OK */
if (!multisupplier_stopped_flag) {
- if (!is_ldif_dump) {
- /* Shut down replication agreements */
- agmtlist_shutdown();
- }
/* if we are cleaning a ruv, stop */
stop_ruv_cleaning();
/* unregister backend state change notification */
@@ -853,6 +851,16 @@ multisupplier_stop(Slapi_PBlock *pb __attribute__((unused)))
return rc;
}
+static int
+multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)))
+{
+ if (!multisupplier_stopped_flag) {
+ /* Shut down replication agreements which will stop all the
+ * replication protocol threads */
+ agmtlist_shutdown();
+ }
+ return 0;
+}
PRBool
multisupplier_started()
@@ -900,6 +908,7 @@ replication_multisupplier_plugin_init(Slapi_PBlock *pb)
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multisupplierdesc);
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)multisupplier_start);
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)multisupplier_stop);
+ rc = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_CLOSE_FN, (void *)multisupplier_pre_stop);
/* Register the plugin interfaces we implement */
/* preop acquires csn generator handle */
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
index d6911f03c..7fc1a16f1 100644
--- a/ldap/servers/slapd/daemon.c
+++ b/ldap/servers/slapd/daemon.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1216,6 +1216,10 @@ slapd_daemon(daemon_ports_t *ports)
task_cancel_all();
}
+ /* Call plugin pre close functions */
+ plugin_pre_closeall();
+
+ /* Now wait for active threads to terminate */
threads = g_get_active_threadcnt();
if (threads > 0) {
slapi_log_err(SLAPI_LOG_INFO, "slapd_daemon",
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
index 76e26cb86..90114dee1 100644
--- a/ldap/servers/slapd/pblock.c
+++ b/ldap/servers/slapd/pblock.c
@@ -1,12 +1,12 @@
-/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005-2025 Red Hat, Inc.
- * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
- * All rights reserved.
- *
- * License: GPL (version 3 or any later version).
- * See LICENSE for details.
- * END COPYRIGHT BLOCK **/
+ /** BEGIN COPYRIGHT BLOCK
+ * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
+ * Copyright (C) 2026 Red Hat, Inc.
+ * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+ * All rights reserved.
+ *
+ * License: GPL (version 3 or any later version).
+ * See LICENSE for details.
+ * END COPYRIGHT BLOCK **/
#ifdef HAVE_CONFIG_H
#include <config.h>
@@ -2524,6 +2524,14 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
}
break;
+ case SLAPI_PLUGIN_PRE_CLOSE_FN:
+ if (pblock->pb_plugin != NULL) {
+ (*(IFP *)value) = pblock->pb_plugin->plg_pre_close;
+ } else {
+ (*(IFP *)value) = NULL;
+ }
+ break;
+
default:
slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_get", "Unknown parameter block argument %d\n", arg);
PR_ASSERT(0);
@@ -4209,6 +4217,10 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
pblock->pb_misc->pb_aci_target_check = *((int *)value);
break;
+ case SLAPI_PLUGIN_PRE_CLOSE_FN:
+ pblock->pb_plugin->plg_pre_close = (IFP)value;
+ break;
+
default:
slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_set",
"Unknown parameter block argument %d\n", arg);
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
index 52d25e19e..95b8de894 100644
--- a/ldap/servers/slapd/plugin.c
+++ b/ldap/servers/slapd/plugin.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1847,6 +1847,28 @@ plugin_dependency_closeall(void)
}
}
+/* Call the pre close functions of all the plugins */
+void
+plugin_pre_closeall(void)
+{
+ Slapi_PBlock *pb = NULL;
+ int plugins_pre_closed = 0;
+ int index = 0;
+
+ while (plugins_pre_closed < global_plugins_started) {
+ if (global_plugin_shutdown_order[index].name) {
+ if (!global_plugin_shutdown_order[index].removed) {
+ pb = slapi_pblock_new();
+ plugin_call_one(global_plugin_shutdown_order[index].plugin,
+ SLAPI_PLUGIN_PRE_CLOSE_FN, pb);
+ slapi_pblock_destroy(pb);
+ }
+ plugins_pre_closed++;
+ }
+ index++;
+ }
+}
+
void
plugin_freeall(void)
{
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 742e3ee14..fd5bc6e71 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2021-2025 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -982,6 +982,7 @@ int plugin_call_exop_plugins(Slapi_PBlock *pb, struct slapdplugin *p);
Slapi_Backend *plugin_extended_op_getbackend(Slapi_PBlock *pb, struct slapdplugin *p);
const char *plugin_extended_op_oid2string(const char *oid);
void plugin_closeall(int close_backends, int close_globals);
+void plugin_pre_closeall(void);
void plugin_dependency_freeall(void);
void plugin_startall(int argc, char **argv, char **plugin_list);
void plugin_get_plugin_dependencies(char *plugin_name, char ***names);
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index fee5a6ab5..64f9f465b 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -1,7 +1,7 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
- * Copyright (C) 2009-2025 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* Contributors:
@@ -1035,6 +1035,7 @@ struct slapdplugin
char *plg_libpath; /* library path for dll/so */
char *plg_initfunc; /* init symbol */
IFP plg_close; /* close function */
+ IFP plg_pre_close; /* pre close function */
Slapi_PluginDesc plg_desc; /* vendor's info */
char *plg_name; /* used for plugin rdn in cn=config */
struct slapdplugin *plg_next; /* for plugin lists */
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 5d9ef289d..4661d2cf0 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -1,6 +1,6 @@
/* BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
* All rights reserved.
*
@@ -7082,6 +7082,7 @@ typedef struct slapi_plugindesc
/* miscellaneous plugin functions */
#define SLAPI_PLUGIN_CLOSE_FN 210
+#define SLAPI_PLUGIN_PRE_CLOSE_FN 211
#define SLAPI_PLUGIN_START_FN 212
#define SLAPI_PLUGIN_CLEANUP_FN 232
#define SLAPI_PLUGIN_POSTSTART_FN 233
--
2.53.0

View File

@ -1,34 +0,0 @@
From 21b9e00264437dff3b6b81355cd159485bfdaf37 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 4 Mar 2026 14:47:01 -0500
Subject: [PATCH] Issue 7271 - Add new plugin pre-close function check to
plugin_invoke_plugin_pb
Description:
In plugin_invoke_plugin_pb we were not checking for the new pre-close function
which led to an error in the logs: pb_op is NULL. In a debug build this leads
to an assertion error at shutdown.
relates: https://github.com/389ds/389-ds-base/issues/7271
Reviewed by: vashirov(Thanks!)
---
ldap/servers/slapd/plugin.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
index 95b8de894..c1b0571eb 100644
--- a/ldap/servers/slapd/plugin.c
+++ b/ldap/servers/slapd/plugin.c
@@ -3647,6 +3647,7 @@ plugin_invoke_plugin_pb(struct slapdplugin *plugin, int operation, Slapi_PBlock
if (operation == SLAPI_PLUGIN_START_FN ||
operation == SLAPI_PLUGIN_POSTSTART_FN ||
operation == SLAPI_PLUGIN_CLOSE_FN ||
+ operation == SLAPI_PLUGIN_PRE_CLOSE_FN ||
operation == SLAPI_PLUGIN_CLEANUP_FN ||
operation == SLAPI_PLUGIN_BE_PRE_CLOSE_FN ||
operation == SLAPI_PLUGIN_BE_POST_OPEN_FN ||
--
2.53.0

View File

@ -46,8 +46,8 @@ ExcludeArch: i686
Summary: 389 Directory Server (base)
Name: 389-ds-base
Version: 2.8.0
Release: 6%{?dist}
Version: 2.9.0
Release: 1%{?dist}
License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND MIT AND MPL-2.0 AND Zlib
URL: https://www.port389.org
Conflicts: selinux-policy-base < 3.9.8
@ -59,13 +59,14 @@ Provides: ldif2ldbm >= 0
##### Bundled cargo crates list - START #####
Provides: bundled(crate(allocator-api2)) = 0.2.21
Provides: bundled(crate(anyhow)) = 1.0.102
Provides: bundled(crate(atty)) = 0.2.14
Provides: bundled(crate(autocfg)) = 1.5.0
Provides: bundled(crate(autocfg)) = 1.5.1
Provides: bundled(crate(base64)) = 0.13.1
Provides: bundled(crate(bitflags)) = 2.10.0
Provides: bundled(crate(bitflags)) = 2.12.1
Provides: bundled(crate(byteorder)) = 1.5.0
Provides: bundled(crate(cbindgen)) = 0.26.0
Provides: bundled(crate(cc)) = 1.2.52
Provides: bundled(crate(cc)) = 1.2.63
Provides: bundled(crate(cfg-if)) = 1.0.4
Provides: bundled(crate(clap)) = 3.2.25
Provides: bundled(crate(clap_lex)) = 0.2.4
@ -75,70 +76,84 @@ Provides: bundled(crate(crossbeam-queue)) = 0.3.12
Provides: bundled(crate(crossbeam-utils)) = 0.8.21
Provides: bundled(crate(equivalent)) = 1.0.2
Provides: bundled(crate(errno)) = 0.3.14
Provides: bundled(crate(fastrand)) = 2.3.0
Provides: bundled(crate(fastrand)) = 2.4.1
Provides: bundled(crate(fernet)) = 0.1.4
Provides: bundled(crate(find-msvc-tools)) = 0.1.7
Provides: bundled(crate(find-msvc-tools)) = 0.1.9
Provides: bundled(crate(foldhash)) = 0.2.0
Provides: bundled(crate(foreign-types)) = 0.3.2
Provides: bundled(crate(foreign-types-shared)) = 0.1.1
Provides: bundled(crate(getrandom)) = 0.3.4
Provides: bundled(crate(hashbrown)) = 0.16.1
Provides: bundled(crate(heck)) = 0.4.1
Provides: bundled(crate(getrandom)) = 0.4.2
Provides: bundled(crate(hashbrown)) = 0.17.1
Provides: bundled(crate(heck)) = 0.5.0
Provides: bundled(crate(hermit-abi)) = 0.1.19
Provides: bundled(crate(indexmap)) = 1.9.3
Provides: bundled(crate(itoa)) = 1.0.17
Provides: bundled(crate(id-arena)) = 2.3.0
Provides: bundled(crate(indexmap)) = 2.14.0
Provides: bundled(crate(itoa)) = 1.0.18
Provides: bundled(crate(jobserver)) = 0.1.34
Provides: bundled(crate(libc)) = 0.2.180
Provides: bundled(crate(linux-raw-sys)) = 0.11.0
Provides: bundled(crate(log)) = 0.4.29
Provides: bundled(crate(lru)) = 0.16.3
Provides: bundled(crate(memchr)) = 2.7.6
Provides: bundled(crate(once_cell)) = 1.21.3
Provides: bundled(crate(openssl)) = 0.10.75
Provides: bundled(crate(leb128fmt)) = 0.1.0
Provides: bundled(crate(libc)) = 0.2.186
Provides: bundled(crate(linux-raw-sys)) = 0.12.1
Provides: bundled(crate(log)) = 0.4.32
Provides: bundled(crate(lru)) = 0.16.4
Provides: bundled(crate(memchr)) = 2.8.1
Provides: bundled(crate(once_cell)) = 1.21.4
Provides: bundled(crate(openssl)) = 0.10.80
Provides: bundled(crate(openssl-macros)) = 0.1.1
Provides: bundled(crate(openssl-sys)) = 0.9.111
Provides: bundled(crate(openssl-sys)) = 0.9.116
Provides: bundled(crate(os_str_bytes)) = 6.6.1
Provides: bundled(crate(paste)) = 0.1.18
Provides: bundled(crate(paste-impl)) = 0.1.18
Provides: bundled(crate(pin-project-lite)) = 0.2.16
Provides: bundled(crate(pkg-config)) = 0.3.32
Provides: bundled(crate(pin-project-lite)) = 0.2.17
Provides: bundled(crate(pkg-config)) = 0.3.33
Provides: bundled(crate(prettyplease)) = 0.2.37
Provides: bundled(crate(proc-macro-hack)) = 0.5.20+deprecated
Provides: bundled(crate(proc-macro2)) = 1.0.105
Provides: bundled(crate(quote)) = 1.0.43
Provides: bundled(crate(r-efi)) = 5.3.0
Provides: bundled(crate(rustix)) = 1.1.3
Provides: bundled(crate(proc-macro2)) = 1.0.106
Provides: bundled(crate(quote)) = 1.0.45
Provides: bundled(crate(r-efi)) = 6.0.0
Provides: bundled(crate(rustix)) = 1.1.4
Provides: bundled(crate(semver)) = 1.0.28
Provides: bundled(crate(serde)) = 1.0.228
Provides: bundled(crate(serde_core)) = 1.0.228
Provides: bundled(crate(serde_derive)) = 1.0.228
Provides: bundled(crate(serde_json)) = 1.0.149
Provides: bundled(crate(shlex)) = 1.3.0
Provides: bundled(crate(serde_json)) = 1.0.150
Provides: bundled(crate(shlex)) = 2.0.1
Provides: bundled(crate(smallvec)) = 1.15.1
Provides: bundled(crate(sptr)) = 0.3.2
Provides: bundled(crate(strsim)) = 0.10.0
Provides: bundled(crate(syn)) = 2.0.114
Provides: bundled(crate(tempfile)) = 3.24.0
Provides: bundled(crate(syn)) = 2.0.117
Provides: bundled(crate(tempfile)) = 3.27.0
Provides: bundled(crate(termcolor)) = 1.4.1
Provides: bundled(crate(textwrap)) = 0.16.2
Provides: bundled(crate(tokio)) = 1.49.0
Provides: bundled(crate(tokio)) = 1.52.3
Provides: bundled(crate(toml)) = 0.5.11
Provides: bundled(crate(tracing)) = 0.1.44
Provides: bundled(crate(tracing-attributes)) = 0.1.31
Provides: bundled(crate(tracing-core)) = 0.1.36
Provides: bundled(crate(unicode-ident)) = 1.0.22
Provides: bundled(crate(unicode-ident)) = 1.0.24
Provides: bundled(crate(unicode-xid)) = 0.2.6
Provides: bundled(crate(uuid)) = 0.8.2
Provides: bundled(crate(vcpkg)) = 0.2.15
Provides: bundled(crate(wasi)) = 0.11.1+wasi_snapshot_preview1
Provides: bundled(crate(wasip2)) = 1.0.1+wasi_0.2.4
Provides: bundled(crate(wasip2)) = 1.0.3+wasi_0.2.9
Provides: bundled(crate(wasip3)) = 0.4.0+wasi_0.3.0_rc_2026_01_06
Provides: bundled(crate(wasm-encoder)) = 0.244.0
Provides: bundled(crate(wasm-metadata)) = 0.244.0
Provides: bundled(crate(wasmparser)) = 0.244.0
Provides: bundled(crate(winapi)) = 0.3.9
Provides: bundled(crate(winapi-i686-pc-windows-gnu)) = 0.4.0
Provides: bundled(crate(winapi-util)) = 0.1.11
Provides: bundled(crate(winapi-x86_64-pc-windows-gnu)) = 0.4.0
Provides: bundled(crate(windows-link)) = 0.2.1
Provides: bundled(crate(windows-sys)) = 0.61.2
Provides: bundled(crate(wit-bindgen)) = 0.46.0
Provides: bundled(crate(wit-bindgen)) = 0.57.1
Provides: bundled(crate(wit-bindgen-core)) = 0.51.0
Provides: bundled(crate(wit-bindgen-rust)) = 0.51.0
Provides: bundled(crate(wit-bindgen-rust-macro)) = 0.51.0
Provides: bundled(crate(wit-component)) = 0.244.0
Provides: bundled(crate(wit-parser)) = 0.244.0
Provides: bundled(crate(zeroize)) = 1.8.2
Provides: bundled(crate(zeroize_derive)) = 1.4.3
Provides: bundled(crate(zmij)) = 1.0.12
Provides: bundled(crate(zmij)) = 1.0.21
##### Bundled cargo crates list - END #####
BuildRequires: nspr-devel >= 4.32
@ -268,47 +283,17 @@ Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download
%endif
Source4: 389-ds-base.sysusers
Source5: vendor-%{version}-5.tar.gz
Source6: Cargo-%{version}-5.lock
Source5: vendor-%{version}-1.tar.gz
Source6: Cargo-%{version}-1.lock
Patch: 0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch
Patch: 0002-Issue-7096-During-replication-online-total-init-the-.patch
Patch: 0003-Issue-Revise-paged-result-search-locking.patch
Patch: 0004-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch
Patch: 0005-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch
Patch: 0006-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch
Patch: 0007-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch
Patch: 0008-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch
Patch: 0009-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch
Patch: 0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch
Patch: 0011-Issue-6476-Fix-build-failure-with-GCC-15.patch
Patch: 0012-Issue-7223-Revert-index-scan-limits-for-system-index.patch
Patch: 0013-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch
Patch: 0014-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch
Patch: 0015-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch
Patch: 0016-Issue-7223-Add-dsctl-index-check-command-for-offline.patch
Patch: 0017-Issue-7096-2nd-During-replication-online-total-init-.patch
Patch: 0018-Issue-7076-6992-6784-6214-Fix-CI-test-failures-7077.patch
Patch: 0019-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch
Patch: 0020-Issue-6947-Fix-health_system_indexes_test.py.patch
Patch: 0021-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch
Patch: 0022-Issue-7150-Compressed-access-log-rotations-skipped-a.patch
Patch: 0023-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch
Patch: 0024-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch
Patch: 0025-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch
Patch: 0026-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch
Patch: 0027-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch
Patch: 0028-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch
Patch: 0029-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch
Patch: 0030-Issue-7066-7052-allow-password-history-to-be-set-to-.patch
Patch: 0031-Issue-7243-UI-fix-certificate-table-and-modal.patch
Patch: 0032-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
Patch: 0033-Issue-7053-Remove-memberof_del_dn_from_groups-from-M.patch
Patch: 0034-Issue-5853-Update-concread-to-0.5.10.patch
Patch: 0035-Issue-7271-plugins-that-create-threads-need-to-updat.patch
Patch: 0036-Security-fix-for-CVE-2025-14905.patch
Patch: 0037-Issue-7271-implement-a-pre-close-plugin-function.patch
Patch: 0038-Issue-7271-Add-new-plugin-pre-close-function-check-t.patch
Patch: 0001-Issue-7554-deref-plugin-null-pointer-dereference-if-.patch
Patch: 0002-Issue-3555-UI-Fix-audit-issue-with-npm-brace-expansi.patch
Patch: 0003-Issue-6922-AddressSanitizer-leaks-found-by-acl-test-.patch
Patch: 0004-Issue-7437-LeakSanitizer-memory-leaks-in-CoS-cache-e.patch
Patch: 0005-Issue-7372-Reindex-adds-tombstones-to-ancestorid-cau.patch
Patch: 0006-Issue-7327-dsctl-healthcheck-DSMOLE0001-inaccurate-r.patch
Patch: 0007-Issue-7267-MDB_BAD_VALSIZE-error-when-updating-index.patch
Patch: 0008-Fix-test389-imports-on-older-branches.patch
%description
389 Directory Server is an LDAPv3 compliant server. The base package includes
@ -758,6 +743,26 @@ exit 0
%endif
%changelog
* Fri Jun 05 2026 Viktor Ashirov <vashirov@redhat.com> - 2.9.0-1
- Bump version to 2.9.0
- Resolves: RHEL-165978 - Replication halt caused by an incorrect setting of "nsslapd-changelogmaxage"
- Resolves: RHEL-166003 - Crash in replica_config_add when manually configuring a replica with an incorrect nsds5ReplicaRoot.
- Resolves: RHEL-166004 - Possible memory leak when using the Retro Changelog plugin.
- Resolves: RHEL-168908 - ns-slapd fails to shutdown when deferred memberof update is in progress
- Resolves: RHEL-168964 - Web console doesn't show the sub suffix of ou=foo,ou=people,dc=example,dc=com. [rhel-9]
- Resolves: RHEL-168972 - Replica installation is failing with message MDB_BAD_VALSIZE: Unsupported size of key/DB name/data, or wrong DUPFIXED [rhel-9]
- Resolves: RHEL-169530 - Rebase 389-ds-base to 2.9.x
- Resolves: RHEL-170270 - DS 12 does not handle escape char in bind user [rhel-9]
- Resolves: RHEL-170275 - dnaSharedConfig: "dnaPortNum: 0" [rhel-9]
- Resolves: RHEL-170280 - Memory leaks in syncrepl plugin during persistent search operations [rhel-9]
- Resolves: RHEL-170287 - access log - suspicious wtime optime negative and large values in internal op [rhel-9]
- Resolves: RHEL-170477 - An online reinitialization with LMDB is terminating the receiving server [rhel-9]
- Resolves: RHEL-170480 - dsctl healthcheck DSMOLE0001 inaccurate recommendations when there is more than 1 LDAP backend [rhel-9]
- Resolves: RHEL-170651 - passwordbadwords attribute of the local password policy is not functioning [rhel-9]
- Resolves: RHEL-170732 - Memory leaks in IPA context on server restart [rhel-9]
- Resolves: RHEL-174525 - [RFE] Add OS-level thread names to all server threads [rhel-9]
- Resolves: RHEL-180717 - Online export is failing when using the option "-s" [rhel-9]
* Thu Mar 05 2026 Viktor Ashirov <vashirov@redhat.com> - 2.8.0-6
- Resolves: RHEL-152335 - Crash in trim_changelog() during the Retro Changelog trimming. [rhel-9.8]

View File

@ -10,7 +10,7 @@
package: [389-ds-base, git, pytest]
- name: clone repo
how: shell
script: git clone -b 389-ds-base-2.8 https://github.com/389ds/389-ds-base /root/ds
script: git clone -b 389-ds-base-2.9 https://github.com/389ds/389-ds-base /root/ds
/test:
/upstream_basic:
test: pytest -v /root/ds/dirsrvtests/tests/suites/basic/basic_test.py

View File

@ -1,4 +1,4 @@
SHA512 (jemalloc-5.3.0.tar.bz2) = 22907bb052096e2caffb6e4e23548aecc5cc9283dce476896a2b1127eee64170e3562fa2e7db9571298814a7a2c7df6e8d1fbe152bd3f3b0c1abec22a2de34b1
SHA512 (389-ds-base-2.8.0.tar.bz2) = 82a1d71618dafa2aaba9e0c54d049dfda0a404fc836887e6283a22eded00a564d66c2988f19c549041db85511a3baa4792d68e89b2de8a5b3647c703c65b75cd
SHA512 (Cargo-2.8.0-5.lock) = efd2512a494c2d8764553b99f911065f41545d55183385889a1e60a5ba65b6514347eef50cea2cc4126abae81e35b2c683b6d99bbe111cef603bc3f04f62c7e9
SHA512 (vendor-2.8.0-5.tar.gz) = 587b478cf1ae57d808495317660ac31a0dcd5ef9df7f7ff6b7ce80e1690de3866fa3d40fa0d67a95413bd88b323e87cea3236454fba02cf1cef673a49092e0f2
SHA512 (389-ds-base-2.9.0.tar.bz2) = 7bbc25185a4df5624a4ec8c3c79c78eecdce03e21548bc7f906263aae5815b3fd464d05d38a2540f378a1085c8b740660b908a82757f4e839928932dcf7da8d0
SHA512 (vendor-2.9.0-1.tar.gz) = 7cddffb4a974201e96baa635fe1c07725f7ee036ac00852fcb7c318a9037d8d87683cc7d8ea4cfe1aa5d3b1af484e56fd946ed2ec2c99cc1d5730835dd304641
SHA512 (Cargo-2.9.0-1.lock) = 967eedd442bd30ba39fc759f32d435c2956487d7d20dd8b922c0c5a4e1c4fe98b649471b3243065ffdf8d810acb8d2b69731d71fe26f2001b787a8b7dbea89a0