From f5244b5b65e749aef208bad7e1d3629fd0b6d74c Mon Sep 17 00:00:00 2001 From: Viktor Ashirov Date: Fri, 9 Jan 2026 13:56:21 +0100 Subject: [PATCH] Bump version to 2.8.0 - Resolves: RHEL-111229 - Error showing local password policy on web UI [rhel-9] - Resolves: RHEL-112680 - Statistics about index lookup report a wrong duration [rhel-9] - Resolves: RHEL-116426 - RetroCL plugin generates invalid LDIF [rhel-9] - Resolves: RHEL-117050 - Replication online reinitialization of a large database gets stalled. [rhel-9] - Resolves: RHEL-117748 - The numSubordinates value is not matching the number of direct children. [rhel-9] - Resolves: RHEL-117771 - When deferred memberof update is enabled after the server crashed it should not launch memberof fixup task by default - Resolves: RHEL-117782 - Ignore the memberOfDeferredUpdate setting when LMDB is used. [rhel-9] - Resolves: RHEL-121170 - Units for changing MDB max size are not consistent across different tools [rhel-9] - Resolves: RHEL-123231 - Improve the way to detect asynchronous operations in the access logs [rhel-9] - Resolves: RHEL-123244 - Attribute uniqueness is not enforced upon modrdn operation [rhel-9] - Resolves: RHEL-123258 - Typo in errors log after a Memberof fixup task. [rhel-9] - Resolves: RHEL-123272 - LDAP high CPU usage while handling indexes with IDL scan limit at INT_MAX [rhel-9] - Resolves: RHEL-123279 - The new ipahealthcheck test ipahealthcheck.ds.backends.BackendsCheck raises CRITICAL issue [rhel-9] - Resolves: RHEL-123368 - IPA health check up script shows time skew is over 24 hours [rhel-9] - Resolves: RHEL-123766 - 389-ds-base OpenScanHub Leaks Detected [rhel-9] - Resolves: RHEL-123893 - Improve output dsctl dbverify when backend does not exist [rhel-9] - Resolves: RHEL-123897 - [WebUI] Replication tab crashes after enabling replication as a consumer [rhel-9] - Resolves: RHEL-123923 - Changelog trimming - add number of scanned entries to the log [rhel-9] - Resolves: RHEL-126552 - RHDS 12.6 doesn't handle 'ldapsearch' filter with space char in DN name correctly [rhel-9] - Resolves: RHEL-129559 - Online initialization of consumers fails with error -23 [rhel-9] - Resolves: RHEL-129580 - Fix paged result search locking [rhel-9] - Resolves: RHEL-138481 - Memory leak observed in ns-slapd with 389-ds-base-2.6.1-12 [rhel-9] - Resolves: RHEL-139825 - Rebase 389-ds-base to 2.8.x - Resolves: RHEL-140089 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication [rhel-9] --- ...e-6377-syntax-error-in-setup.py-6378.patch | 40 - ...etroCL-plugin-generates-invalid-LDIF.patch | 80 + ...9-replica.py-is-using-nonexistent-da.patch | 37 - ...g-replication-online-total-init-the-.patch | 318 +++ ...stance-read-only-mode-is-broken-6681.patch | 351 ---- ...e-Revise-paged-result-search-locking.patch | 765 ++++++++ ...N-Access-Control-Plugin-with-wildcar.patch | 125 -- ...-ordering-mismatch-after-upgrade-717.patch | 213 ++ ...ronise-accept_thread-with-slapd_daem.patch | 50 - ...ue-6782-Improve-paged-result-locking.patch | 127 -- ...nd-creation-cleanup-and-Database-UI-.patch | 488 ----- ...iq-allow-specifying-match-rules-in-t.patch | 45 - ...I-Properly-handle-disabled-NDN-cache.patch | 1201 ------------ ...ilter-is-not-fully-applying-matching.patch | 399 ---- ...essed-log-rotation-creates-files-wit.patch | 163 -- ...nt-repeated-disconnect-logs-during-s.patch | 116 -- ...f-Replicas-with-the-consumer-role-al.patch | 67 - ...ser-that-is-updated-during-password-.patch | 143 -- ...-if-repl-keep-alive-entry-can-not-be.patch | 98 - ...est-for-entryUSN-overflow-on-failed-.patch | 352 ---- ...est-for-numSubordinates-replication-.patch | 172 -- ...k-password-hashes-in-audit-logs-6885.patch | 814 -------- ...isk-monitoring-test-failures-and-imp.patch | 1721 ----------------- ...ss-Coverity-scan-issues-in-memberof-.patch | 63 - ...6468-CLI-Fix-default-error-log-level.patch | 31 - ...13-6886-6250-Adjust-xfail-marks-6914.patch | 222 --- ...llow-system-to-manage-uid-gid-at-sta.patch | 32 - ...y-leak-in-roles_cache_create_object_.patch | 92 - ...y-leak-in-roles_cache_create_object_.patch | 262 --- ...essSanitizer-memory-leak-in-mdb_init.patch | 65 - ...8-AddressSanitizer-leak-in-do_search.patch | 58 - ...ssSanitizer-leak-in-agmt_update_init.patch | 58 - ...apd-crashes-when-a-referral-is-added.patch | 97 - ...f-monitor-server-fails-with-ldapi-du.patch | 268 --- ...bordinates-tombstoneNumSubordinates-.patch | 1448 -------------- ...ssue-6910-Fix-latest-coverity-issues.patch | 559 ------ ...lation-failure-with-rust-1.89-on-Fed.patch | 35 - 389-ds-base-git-local.sh | 23 - 389-ds-base-git.sh | 16 - 389-ds-base.spec | 364 +++- main.fmf | 2 +- sources | 6 +- 42 files changed, 1651 insertions(+), 9935 deletions(-) delete mode 100644 0001-Issue-6377-syntax-error-in-setup.py-6378.patch create mode 100644 0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch delete mode 100644 0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch create mode 100644 0002-Issue-7096-During-replication-online-total-init-the-.patch delete mode 100644 0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch create mode 100644 0003-Issue-Revise-paged-result-search-locking.patch delete mode 100644 0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch create mode 100644 0004-Issue-7172-Index-ordering-mismatch-after-upgrade-717.patch delete mode 100644 0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch delete mode 100644 0006-Issue-6782-Improve-paged-result-locking.patch delete mode 100644 0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch delete mode 100644 0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch delete mode 100644 0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch delete mode 100644 0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch delete mode 100644 0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch delete mode 100644 0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch delete mode 100644 0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch delete mode 100644 0014-Issue-6893-Log-user-that-is-updated-during-password-.patch delete mode 100644 0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch delete mode 100644 0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch delete mode 100644 0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch delete mode 100644 0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch delete mode 100644 0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch delete mode 100644 0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch delete mode 100644 0021-Issue-6468-CLI-Fix-default-error-log-level.patch delete mode 100644 0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch delete mode 100644 0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch delete mode 100644 0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch delete mode 100644 0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch delete mode 100644 0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch delete mode 100644 0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch delete mode 100644 0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch delete mode 100644 0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch delete mode 100644 0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch delete mode 100644 0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch delete mode 100644 0032-Issue-6910-Fix-latest-coverity-issues.patch delete mode 100644 0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch delete mode 100644 389-ds-base-git-local.sh delete mode 100644 389-ds-base-git.sh diff --git a/0001-Issue-6377-syntax-error-in-setup.py-6378.patch b/0001-Issue-6377-syntax-error-in-setup.py-6378.patch deleted file mode 100644 index 84ff8d0..0000000 --- a/0001-Issue-6377-syntax-error-in-setup.py-6378.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 5903fac2334f984d18aea663735fb260d6b100ed Mon Sep 17 00:00:00 2001 -From: progier389 -Date: Tue, 22 Oct 2024 17:26:46 +0200 -Subject: [PATCH] Issue 6377 - syntax error in setup.py (#6378) - -Syntax error due to badly nested quotes in dblib.py cause trouble in setup.py and dsconf dblib b2b2mdb/mdb2dbd -Fix bit using double quotes in the f-expression and quotes for the embedded strings. - -Issue: #6377 - -Reviewed by: @tbordaz, @droideck (Thank!) ---- - src/lib389/lib389/cli_ctl/dblib.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/lib389/lib389/cli_ctl/dblib.py b/src/lib389/lib389/cli_ctl/dblib.py -index ff81f0e19..3f6e7b456 100644 ---- a/src/lib389/lib389/cli_ctl/dblib.py -+++ b/src/lib389/lib389/cli_ctl/dblib.py -@@ -183,7 +183,7 @@ def export_changelog(be, dblib): - return False - try: - cl5dbname = be['eccl5dbname'] if dblib == "bdb" else be['cl5dbname'] -- _log.info(f'Exporting changelog {cl5dbname} to {be['cl5name']}') -+ _log.info(f"Exporting changelog {cl5dbname} to {be['cl5name']}") - run_dbscan(['-D', dblib, '-f', cl5dbname, '-X', be['cl5name']]) - return True - except subprocess.CalledProcessError as e: -@@ -194,7 +194,7 @@ def import_changelog(be, dblib): - # import backend changelog - try: - cl5dbname = be['eccl5dbname'] if dblib == "bdb" else be['cl5dbname'] -- _log.info(f'Importing changelog {cl5dbname} from {be['cl5name']}') -+ _log.info(f"Importing changelog {cl5dbname} from {be['cl5name']}") - run_dbscan(['-D', dblib, '-f', cl5dbname, '--import', be['cl5name'], '--do-it']) - return True - except subprocess.CalledProcessError as e: --- -2.49.0 - diff --git a/0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch b/0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch new file mode 100644 index 0000000..f4ae039 --- /dev/null +++ b/0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch @@ -0,0 +1,80 @@ +From 94a52cd03a6fcccf78a4d7e5c4adc65fb777fcfb Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +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 + diff --git a/0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch b/0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch deleted file mode 100644 index 5a04986..0000000 --- a/0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch +++ /dev/null @@ -1,37 +0,0 @@ -From a91c2641646824e44ef3b31a7eea238e3f55e5c3 Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Tue, 1 Jul 2025 12:44:04 +0200 -Subject: [PATCH] Issue 6838 - lib389/replica.py is using nonexistent - datetime.UTC in Python 3.9 - -Bug Description: -389-ds-base-2.x is supposed to be used with Python 3.9. -But lib389/replica.py is using `datetime.UTC`, which is an alias -to `datetime.timezone.utc` was added only in Python 3.11. - -Fix Description: -Use `datetime.timezone.utc` instead. - -Fixes: https://github.com/389ds/389-ds-base/issues/6838 - -Reviewed by: @mreynolds389 (Thanks!) ---- - src/lib389/lib389/replica.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py -index 8791f7f4c..78d6eb4eb 100644 ---- a/src/lib389/lib389/replica.py -+++ b/src/lib389/lib389/replica.py -@@ -917,7 +917,7 @@ class RUV(object): - ValueError("Wrong CSN value was supplied") - - timestamp = int(csn[:8], 16) -- time_str = datetime.datetime.fromtimestamp(timestamp, datetime.UTC).strftime('%Y-%m-%d %H:%M:%S') -+ time_str = datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S') - # We are parsing shorter CSN which contains only timestamp - if len(csn) == 8: - return time_str --- -2.49.0 - diff --git a/0002-Issue-7096-During-replication-online-total-init-the-.patch b/0002-Issue-7096-During-replication-online-total-init-the-.patch new file mode 100644 index 0000000..4d86c59 --- /dev/null +++ b/0002-Issue-7096-During-replication-online-total-init-the-.patch @@ -0,0 +1,318 @@ +From 67118939bd2b51e53f7284c2058a43744d1dba76 Mon Sep 17 00:00:00 2001 +From: tbordaz +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 + diff --git a/0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch b/0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch deleted file mode 100644 index ac0af7b..0000000 --- a/0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch +++ /dev/null @@ -1,351 +0,0 @@ -From 4eef34cec551582d1de23266bc6cde84a7e38b5d Mon Sep 17 00:00:00 2001 -From: progier389 -Date: Mon, 24 Mar 2025 10:43:21 +0100 -Subject: [PATCH] Issue 6680 - instance read-only mode is broken (#6681) - -Read only mode is broken because some plugins fails to starts as they are not able to create/updates some entries in the dse backend. -Solution is to allow interrnal operations to write in dse.backend but not modify the dse.ldif (except for the special case when trying to modify nsslapd-readonly flags (to be allowed to set/unset the readonly mode) - -Issue: #6680 - -Reviewed by: @droideck, @tbordaz (thanks!) ---- - .../tests/suites/config/regression_test.py | 60 ++++++++++ - ldap/servers/slapd/dse.c | 110 +++++++++++++++++- - ldap/servers/slapd/mapping_tree.c | 90 ++++++++++++-- - 3 files changed, 247 insertions(+), 13 deletions(-) - -diff --git a/dirsrvtests/tests/suites/config/regression_test.py b/dirsrvtests/tests/suites/config/regression_test.py -index 8dbba8cd2..6e313ac8a 100644 ---- a/dirsrvtests/tests/suites/config/regression_test.py -+++ b/dirsrvtests/tests/suites/config/regression_test.py -@@ -28,6 +28,8 @@ CUSTOM_MEM = '9100100100' - IDLETIMEOUT = 5 - DN_TEST_USER = f'uid={TEST_USER_PROPERTIES["uid"]},ou=People,{DEFAULT_SUFFIX}' - -+RO_ATTR = 'nsslapd-readonly' -+ - - @pytest.fixture(scope="module") - def idletimeout_topo(topo, request): -@@ -190,3 +192,61 @@ def test_idletimeout(idletimeout_topo, dn, expected_result): - except ldap.SERVER_DOWN: - result = True - assert expected_result == result -+ -+ -+def test_instance_readonly_mode(topo): -+ """Check that readonly mode is supported -+ -+ :id: 34d2e28e-04d7-11f0-b0cf-482ae39447e5 -+ :setup: Standalone Instance -+ :steps: -+ 1. Set readonly mode -+ 2. Stop the instance -+ 3. Get dse.ldif modification time -+ 4. Start the instance -+ 5. Get dse.ldif modification time -+ 6. Check that modification time has not changed -+ 7. Check that readonly mode is set -+ 8. Try to modify another config attribute -+ 9. Unset readonly mode -+ 10. Restart the instance -+ 11. Check that modification time has not changed -+ 12. Check that modification time has changed -+ 13. Check that readonly mode is unset -+ 14. Try to modify another config attribute -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Success -+ 6. Success -+ 7. Success -+ 8. Should get ldap.UNWILLING_TO_PERFORM exception -+ 9. Success -+ 10. Success -+ 11. Success -+ 12. Success -+ 13. Success -+ 14. Success -+ """ -+ -+ inst = topo.standalone -+ dse_path = f'{topo.standalone.get_config_dir()}/dse.ldif' -+ inst.config.replace(RO_ATTR, 'on') -+ inst.stop() -+ dse_mtime = os.stat(dse_path).st_mtime -+ inst.start() -+ new_dse_mtime = os.stat(dse_path).st_mtime -+ assert dse_mtime == new_dse_mtime -+ assert inst.config.get_attr_val_utf8(RO_ATTR) == "on" -+ attr = 'nsslapd-errorlog-maxlogsize' -+ val = inst.config.get_attr_val_utf8(attr) -+ with pytest.raises(ldap.UNWILLING_TO_PERFORM): -+ inst.config.replace(attr, val) -+ inst.config.replace(RO_ATTR, 'off') -+ inst.restart() -+ new_dse_mtime = os.stat(dse_path).st_mtime -+ assert dse_mtime != new_dse_mtime -+ assert inst.config.get_attr_val_utf8(RO_ATTR) == "off" -+ inst.config.replace(attr, val) -diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c -index e3157c1ce..0f266f0d7 100644 ---- a/ldap/servers/slapd/dse.c -+++ b/ldap/servers/slapd/dse.c -@@ -1031,6 +1031,114 @@ dse_check_for_readonly_error(Slapi_PBlock *pb, struct dse *pdse) - return rc; /* no error */ - } - -+/* Trivial wrapper around slapi_re_comp to handle errors */ -+static Slapi_Regex * -+recomp(const char *regexp) -+{ -+ char *error = ""; -+ Slapi_Regex *re = slapi_re_comp(regexp, &error); -+ if (re == NULL) { -+ slapi_log_err(SLAPI_LOG_ERR, "is_readonly_set_in_dse", -+ "Failed to compile '%s' regular expression. Error is %s\n", -+ regexp, error); -+ } -+ slapi_ch_free_string(&error); -+ return re; -+} -+ -+/* -+ * Check if "nsslapd-readonly: on" is in cn-config in dse.ldif file -+ * ( If the flag is set in memory but on in the file, the file should -+ * be written (to let dsconf able to modify the nsslapd-readonly flag) -+ */ -+static bool -+is_readonly_set_in_dse(const char *dsename) -+{ -+ Slapi_Regex *re_config = recomp("^dn:\\s+cn=config\\s*$"); -+ Slapi_Regex *re_isro = recomp("^" CONFIG_READONLY_ATTRIBUTE ":\\s+on\\s*$"); -+ Slapi_Regex *re_eoe = recomp("^$"); -+ bool isconfigentry = false; -+ bool isro = false; -+ FILE *fdse = NULL; -+ char line[128]; -+ char *error = NULL; -+ const char *regexp = ""; -+ -+ if (!dsename) { -+ goto done; -+ } -+ if (re_config == NULL || re_isro == NULL || re_eoe == NULL) { -+ goto done; -+ } -+ fdse = fopen(dsename, "r"); -+ if (fdse == NULL) { -+ /* No dse file, we need to write it */ -+ goto done; -+ } -+ while (fgets(line, (sizeof line), fdse)) { -+ /* Convert the read line to lowercase */ -+ for (char *pt=line; *pt; pt++) { -+ if (isalpha(*pt)) { -+ *pt = tolower(*pt); -+ } -+ } -+ if (slapi_re_exec_nt(re_config, line)) { -+ isconfigentry = true; -+ } -+ if (slapi_re_exec_nt(re_eoe, line)) { -+ if (isconfigentry) { -+ /* End of config entry ==> readonly flag is not set */ -+ break; -+ } -+ } -+ if (isconfigentry && slapi_re_exec_nt(re_isro, line)) { -+ /* Found readonly flag */ -+ isro = true; -+ break; -+ } -+ } -+done: -+ if (fdse) { -+ (void) fclose(fdse); -+ } -+ slapi_re_free(re_config); -+ slapi_re_free(re_isro); -+ slapi_re_free(re_eoe); -+ return isro; -+} -+ -+/* -+ * Check if dse.ldif can be written -+ * Beware that even in read-only mode dse.ldif file -+ * should still be written to change the nsslapd-readonly value -+ */ -+static bool -+check_if_readonly(struct dse *pdse) -+{ -+ static bool ro = false; -+ -+ if (pdse->dse_filename == NULL) { -+ return false; -+ } -+ if (!slapi_config_get_readonly()) { -+ ro = false; -+ return ro; -+ } -+ if (ro) { -+ /* read-only mode and dse is up to date ==> Do not modify it. */ -+ return ro; -+ } -+ /* First attempt to write the dse.ldif since readonly mode is enabled. -+ * Lets check if "nsslapd-readonly: on" is in cn=config entry -+ * and allow to write the dse.ldif if it is the case -+ */ -+ if (is_readonly_set_in_dse(pdse->dse_filename)) { -+ /* read-only mode and dse is up to date ==> Do not modify it. */ -+ ro = true; -+ } -+ /* Read only mode but nsslapd-readonly value is not up to date. */ -+ return ro; -+} - - /* - * Write the AVL tree of entries back to the LDIF file. -@@ -1041,7 +1149,7 @@ dse_write_file_nolock(struct dse *pdse) - FPWrapper fpw; - int rc = 0; - -- if (dont_ever_write_dse_files) { -+ if (dont_ever_write_dse_files || check_if_readonly(pdse)) { - return rc; - } - -diff --git a/ldap/servers/slapd/mapping_tree.c b/ldap/servers/slapd/mapping_tree.c -index dd7b1af37..e51b3b948 100644 ---- a/ldap/servers/slapd/mapping_tree.c -+++ b/ldap/servers/slapd/mapping_tree.c -@@ -2058,6 +2058,82 @@ slapi_dn_write_needs_referral(Slapi_DN *target_sdn, Slapi_Entry **referral) - done: - return ret; - } -+ -+/* -+ * This function dermines if an operation should be rejected -+ * when readonly mode is enabled. -+ * All operations are rejected except: -+ * - if they target a private backend that is not the DSE backend -+ * - if they are read operations (SEARCH, COMPARE, BIND, UNBIND) -+ * - if they are tombstone fixup operation (i.e: tombstone purging) -+ * - if they are internal operation that targets the DSE backend. -+ * (change will then be done in memory but not written in dse.ldif) -+ * - single modify modify operation on cn=config changing nsslapd-readonly -+ * (to allow "dsconf instance config replace nsslapd-readonly=xxx", -+ change will then be done both in memory and in dse.ldif) -+ */ -+static bool -+is_rejected_op(Slapi_Operation *op, Slapi_Backend *be) -+{ -+ const char *betype = slapi_be_gettype(be); -+ unsigned long be_op_type = operation_get_type(op); -+ int isdse = (betype && strcmp(betype, "DSE") == 0); -+ -+ /* Private backend operations are not rejected */ -+ -+ /* Read operations are not rejected */ -+ if ((be_op_type == SLAPI_OPERATION_SEARCH) || -+ (be_op_type == SLAPI_OPERATION_COMPARE) || -+ (be_op_type == SLAPI_OPERATION_BIND) || -+ (be_op_type == SLAPI_OPERATION_UNBIND)) { -+ return false; -+ } -+ -+ /* Tombstone fixup are not rejected. */ -+ if (operation_is_flag_set(op, OP_FLAG_TOMBSTONE_FIXUP)) { -+ return false; -+ } -+ -+ if (!isdse) { -+ /* write operation on readonly backends are rejected */ -+ if (be->be_readonly) { -+ return true; -+ } -+ -+ /* private backends (DSE excepted) are not backed on files -+ * so write operations are accepted. -+ * but other operations (not on DSE) are rejected. -+ */ -+ if (slapi_be_private(be)) { -+ return false; -+ } else { -+ return true; -+ } -+ } -+ -+ /* Allowed operations in dse backend are: -+ * - the internal operations and -+ * - modify of nsslapd-readonly flag in cn=config -+ */ -+ -+ if (operation_is_flag_set(op, OP_FLAG_INTERNAL)) { -+ return false; -+ } -+ if (be_op_type == SLAPI_OPERATION_MODIFY) { -+ Slapi_DN *sdn = operation_get_target_spec(op); -+ Slapi_DN config = {0}; -+ LDAPMod **mods = op->o_params.p.p_modify.modify_mods; -+ slapi_sdn_init_ndn_byref(&config, SLAPD_CONFIG_DN); -+ if (mods && mods[0] && !mods[1] && -+ slapi_sdn_compare(sdn, &config) == 0 && -+ strcasecmp(mods[0]->mod_type, CONFIG_READONLY_ATTRIBUTE) == 0) { -+ /* Single modifier impacting nsslapd-readonly */ -+ return false; -+ } -+ } -+ return true; -+} -+ - /* - * Description: - * The reason we have a mapping tree. This function selects a backend or -@@ -2095,7 +2171,6 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re - int ret; - int scope = LDAP_SCOPE_BASE; - int op_type; -- int fixup = 0; - - if (slapi_atomic_load_32(&mapping_tree_freed, __ATOMIC_RELAXED)) { - /* shutdown detected */ -@@ -2112,7 +2187,6 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re - - /* Get the target for this op */ - target_sdn = operation_get_target_spec(op); -- fixup = operation_is_flag_set(op, OP_FLAG_TOMBSTONE_FIXUP); - - PR_ASSERT(mapping_tree_inited == 1); - -@@ -2161,22 +2235,14 @@ slapi_mapping_tree_select(Slapi_PBlock *pb, Slapi_Backend **be, Slapi_Entry **re - * or if the whole server is readonly AND backend is public (!private) - */ - if ((ret == LDAP_SUCCESS) && *be && !be_isdeleted(*be) && -- (((*be)->be_readonly && !fixup) || -- ((slapi_config_get_readonly() && !fixup) && -- !slapi_be_private(*be)))) { -- unsigned long be_op_type = operation_get_type(op); -- -- if ((be_op_type != SLAPI_OPERATION_SEARCH) && -- (be_op_type != SLAPI_OPERATION_COMPARE) && -- (be_op_type != SLAPI_OPERATION_BIND) && -- (be_op_type != SLAPI_OPERATION_UNBIND)) { -+ ((*be)->be_readonly || slapi_config_get_readonly()) && -+ is_rejected_op(op, *be)) { - if (errorbuf) { - PL_strncpyz(errorbuf, slapi_config_get_readonly() ? "Server is read-only" : "database is read-only", ebuflen); - } - ret = LDAP_UNWILLING_TO_PERFORM; - slapi_be_Unlock(*be); - *be = NULL; -- } - } - - return ret; --- -2.49.0 - diff --git a/0003-Issue-Revise-paged-result-search-locking.patch b/0003-Issue-Revise-paged-result-search-locking.patch new file mode 100644 index 0000000..3e0fcc8 --- /dev/null +++ b/0003-Issue-Revise-paged-result-search-locking.patch @@ -0,0 +1,765 @@ +From a0af5ab79ddad7944687e31f16bb2a667c800958 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +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 + #include /* 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 + +@@ -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 + diff --git a/0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch b/0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch deleted file mode 100644 index 67ee402..0000000 --- a/0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 5613937623f0037a54490b22c60f7eb1aa52cf4e Mon Sep 17 00:00:00 2001 -From: James Chapman -Date: Wed, 25 Jun 2025 14:11:05 +0000 -Subject: [PATCH] =?UTF-8?q?Issue=206825=20-=20RootDN=20Access=20Control=20?= - =?UTF-8?q?Plugin=20with=20wildcards=20for=20IP=20addre=E2=80=A6=20(#6826)?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Bug description: -RootDN Access Control Plugin with wildcards for IP addresses fails withi -an error "Invalid IP address" - -socket.inet_aton() validates IPv4 IP addresses and does not support wildcards. - -Fix description: -Add a regex pattern to match wildcard IP addresses, check each octet is -between 0-255 - -Fixes: https://github.com/389ds/389-ds-base/issues/6825 - -Reviewed by: @droideck (Thank you) ---- - .../lib389/cli_conf/plugins/rootdn_ac.py | 16 +++----- - src/lib389/lib389/utils.py | 40 +++++++++++++++++++ - 2 files changed, 45 insertions(+), 11 deletions(-) - -diff --git a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py -index 65486fff8..1456f5ebe 100644 ---- a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py -+++ b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py -@@ -8,7 +8,7 @@ - - import socket - from lib389.plugins import RootDNAccessControlPlugin --from lib389.utils import is_valid_hostname -+from lib389.utils import is_valid_hostname, is_valid_ip - from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit - from lib389.cli_base import CustomHelpFormatter - -@@ -62,19 +62,13 @@ def validate_args(args): - - if args.allow_ip is not None: - for ip in args.allow_ip: -- if ip != "delete": -- try: -- socket.inet_aton(ip) -- except socket.error: -- raise ValueError(f"Invalid IP address ({ip}) for '--allow-ip'") -+ if ip != "delete" and not is_valid_ip(ip): -+ raise ValueError(f"Invalid IP address ({ip}) for '--allow-ip'") - - if args.deny_ip is not None and args.deny_ip != "delete": - for ip in args.deny_ip: -- if ip != "delete": -- try: -- socket.inet_aton(ip) -- except socket.error: -- raise ValueError(f"Invalid IP address ({ip}) for '--deny-ip'") -+ if ip != "delete" and not is_valid_ip(ip): -+ raise ValueError(f"Invalid IP address ({ip}) for '--deny-ip'") - - if args.allow_host is not None: - for hostname in args.allow_host: -diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py -index afc282e94..3937fc1a8 100644 ---- a/src/lib389/lib389/utils.py -+++ b/src/lib389/lib389/utils.py -@@ -31,6 +31,7 @@ import logging - import shutil - import ldap - import socket -+import ipaddress - import time - import stat - from datetime import (datetime, timedelta) -@@ -1707,6 +1708,45 @@ def is_valid_hostname(hostname): - allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(? +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 backend index set --add-mr integerOrderingMatch --attr %s %s && " ++ "dsconf 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 + diff --git a/0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch b/0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch deleted file mode 100644 index 8aa4af0..0000000 --- a/0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch +++ /dev/null @@ -1,50 +0,0 @@ -From b8cac173ca2549d2142332107e06fcb4bd34bd65 Mon Sep 17 00:00:00 2001 -From: James Chapman -Date: Fri, 8 Mar 2024 16:15:52 +0000 -Subject: [PATCH] Issue 6119 - Synchronise accept_thread with slapd_daemon - (#6120) - -Bug Description: A corner cases exists, where the slapd_daemon has -begun its shutdown process but the accept_thread is still running -and capable of handling new connections. When this scenario occurs, -the connection subsystem has been partially deallocated and is in -an unstable state. A segfault is generated when attempting to get a -new connection from the connection table. - -Fix Description: The connection table is only deallocated when the -number of active threads is 0. Modify the accept_thread to adjust the -the active thread count during creation/destruction, meaning the connection -table can only be freed when the accept_thread has completed - -Relates: https://github.com/389ds/389-ds-base/issues/6119 - -Reviewed by: @tbordaz, @Firstyear , @mreynolds389 (Thank you) ---- - ldap/servers/slapd/daemon.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c -index 5d01a2526..a43fc9285 100644 ---- a/ldap/servers/slapd/daemon.c -+++ b/ldap/servers/slapd/daemon.c -@@ -868,6 +868,8 @@ accept_thread(void *vports) - slapi_ch_free((void **)&listener_idxs); - slapd_sockets_ports_free(ports); - slapi_ch_free((void **)&fds); -+ g_decr_active_threadcnt(); -+ slapi_log_err(SLAPI_LOG_INFO, "slapd_daemon", "slapd shutting down - accept_thread\n"); - } - - void -@@ -1158,6 +1160,8 @@ slapd_daemon(daemon_ports_t *ports) - slapi_log_err(SLAPI_LOG_EMERG, "slapd_daemon", "Unable to fd accept thread - Shutting Down (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)\n", - errorCode, slapd_pr_strerror(errorCode)); - g_set_shutdown(SLAPI_SHUTDOWN_EXIT); -+ } else{ -+ g_incr_active_threadcnt(); - } - - #ifdef WITH_SYSTEMD --- -2.49.0 - diff --git a/0006-Issue-6782-Improve-paged-result-locking.patch b/0006-Issue-6782-Improve-paged-result-locking.patch deleted file mode 100644 index 5c9558b..0000000 --- a/0006-Issue-6782-Improve-paged-result-locking.patch +++ /dev/null @@ -1,127 +0,0 @@ -From 7943443bb92fca6676922349fb12503a527cb6b1 Mon Sep 17 00:00:00 2001 -From: Mark Reynolds -Date: Thu, 15 May 2025 10:35:27 -0400 -Subject: [PATCH] Issue 6782 - Improve paged result locking - -Description: - -When cleaning a slot, instead of mem setting everything to Zero and restoring -the mutex, manually reset all the values leaving the mutex pointer -intact. - -There is also a deadlock possibility when checking for abandoned PR search -in opshared.c, and we were checking a flag value outside of the per_conn -lock. - -Relates: https://github.com/389ds/389-ds-base/issues/6782 - -Reviewed by: progier & spichugi(Thanks!!) ---- - ldap/servers/slapd/opshared.c | 10 +++++++++- - ldap/servers/slapd/pagedresults.c | 27 +++++++++++++++++---------- - 2 files changed, 26 insertions(+), 11 deletions(-) - -diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c -index 7dc2d5983..14a7dcdfb 100644 ---- a/ldap/servers/slapd/opshared.c -+++ b/ldap/servers/slapd/opshared.c -@@ -592,6 +592,14 @@ 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() -+ */ - pagedresults_mutex = pageresult_lock_get_addr(pb_conn); - } - -@@ -717,11 +725,11 @@ op_shared_search(Slapi_PBlock *pb, int send_result) - pr_search_result = pagedresults_get_search_result(pb_conn, operation, 1 /*locked*/, pr_idx); - if (pr_search_result) { - if (pagedresults_is_abandoned_or_notavailable(pb_conn, 1 /*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; -- pthread_mutex_unlock(pagedresults_mutex); - goto free_and_return; - } else { - slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, pr_search_result); -diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c -index 642aefb3d..c3f3aae01 100644 ---- a/ldap/servers/slapd/pagedresults.c -+++ b/ldap/servers/slapd/pagedresults.c -@@ -48,7 +48,6 @@ pageresult_lock_get_addr(Connection *conn) - static void - _pr_cleanup_one_slot(PagedResults *prp) - { -- PRLock *prmutex = NULL; - if (!prp) { - return; - } -@@ -56,13 +55,17 @@ _pr_cleanup_one_slot(PagedResults *prp) - /* sr is left; release it. */ - prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set)); - } -- /* clean up the slot */ -- if (prp->pr_mutex) { -- /* pr_mutex is reused; back it up and reset it. */ -- prmutex = prp->pr_mutex; -- } -- memset(prp, '\0', sizeof(PagedResults)); -- prp->pr_mutex = prmutex; -+ -+ /* clean up the slot except the mutex */ -+ prp->pr_current_be = NULL; -+ prp->pr_search_result_set = NULL; -+ prp->pr_search_result_count = 0; -+ prp->pr_search_result_set_size_estimate = 0; -+ prp->pr_sort_result_code = 0; -+ prp->pr_timelimit_hr.tv_sec = 0; -+ prp->pr_timelimit_hr.tv_nsec = 0; -+ prp->pr_flags = 0; -+ prp->pr_msgid = 0; - } - - /* -@@ -1007,7 +1010,8 @@ op_set_pagedresults(Operation *op) - - /* - * pagedresults_lock/unlock -- introduced to protect search results for the -- * asynchronous searches. -+ * 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) -@@ -1045,6 +1049,8 @@ int - pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index) - { - PagedResults *prp; -+ int32_t result; -+ - if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) { - return 1; /* not abandoned, but do not want to proceed paged results op. */ - } -@@ -1052,10 +1058,11 @@ pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int inde - pthread_mutex_lock(pageresult_lock_get_addr(conn)); - } - prp = conn->c_pagedresults.prl_list + index; -+ result = prp->pr_flags & CONN_FLAG_PAGEDRESULTS_ABANDONED; - if (!locked) { - pthread_mutex_unlock(pageresult_lock_get_addr(conn)); - } -- return prp->pr_flags & CONN_FLAG_PAGEDRESULTS_ABANDONED; -+ return result; - } - - int --- -2.49.0 - diff --git a/0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch b/0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch deleted file mode 100644 index cd0029d..0000000 --- a/0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch +++ /dev/null @@ -1,488 +0,0 @@ -From b6729a99f3a3d4c6ebe82d4bb60ea2a6f8727782 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Fri, 27 Jun 2025 18:43:39 -0700 -Subject: [PATCH] Issue 6822 - Backend creation cleanup and Database UI tab - error handling (#6823) - -Description: Add rollback functionality when mapping tree creation fails -during backend creation to prevent orphaned backends. -Improve error handling in Database, Replication and Monitoring UI tabs -to gracefully handle backend get-tree command failures. - -Fixes: https://github.com/389ds/389-ds-base/issues/6822 - -Reviewed by: @mreynolds389 (Thanks!) ---- - src/cockpit/389-console/src/database.jsx | 119 ++++++++------ - src/cockpit/389-console/src/monitor.jsx | 172 +++++++++++--------- - src/cockpit/389-console/src/replication.jsx | 55 ++++--- - src/lib389/lib389/backend.py | 18 +- - 4 files changed, 210 insertions(+), 154 deletions(-) - -diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx -index c0c4be414..276125dfc 100644 ---- a/src/cockpit/389-console/src/database.jsx -+++ b/src/cockpit/389-console/src/database.jsx -@@ -478,6 +478,59 @@ export class Database extends React.Component { - } - - loadSuffixTree(fullReset) { -+ const treeData = [ -+ { -+ name: _("Global Database Configuration"), -+ icon: , -+ id: "dbconfig", -+ }, -+ { -+ name: _("Chaining Configuration"), -+ icon: , -+ id: "chaining-config", -+ }, -+ { -+ name: _("Backups & LDIFs"), -+ icon: , -+ id: "backups", -+ }, -+ { -+ name: _("Password Policies"), -+ id: "pwp", -+ icon: , -+ children: [ -+ { -+ name: _("Global Policy"), -+ icon: , -+ id: "pwpolicy", -+ }, -+ { -+ name: _("Local Policies"), -+ icon: , -+ id: "localpwpolicy", -+ }, -+ ], -+ defaultExpanded: true -+ }, -+ { -+ name: _("Suffixes"), -+ icon: , -+ id: "suffixes-tree", -+ children: [], -+ defaultExpanded: true, -+ action: ( -+ -+ ), -+ } -+ ]; -+ - const cmd = [ - "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", - "backend", "get-tree", -@@ -491,58 +544,20 @@ export class Database extends React.Component { - suffixData = JSON.parse(content); - this.processTree(suffixData); - } -- const treeData = [ -- { -- name: _("Global Database Configuration"), -- icon: , -- id: "dbconfig", -- }, -- { -- name: _("Chaining Configuration"), -- icon: , -- id: "chaining-config", -- }, -- { -- name: _("Backups & LDIFs"), -- icon: , -- id: "backups", -- }, -- { -- name: _("Password Policies"), -- id: "pwp", -- icon: , -- children: [ -- { -- name: _("Global Policy"), -- icon: , -- id: "pwpolicy", -- }, -- { -- name: _("Local Policies"), -- icon: , -- id: "localpwpolicy", -- }, -- ], -- defaultExpanded: true -- }, -- { -- name: _("Suffixes"), -- icon: , -- id: "suffixes-tree", -- children: suffixData, -- defaultExpanded: true, -- action: ( -- -- ), -- } -- ]; -+ -+ let current_node = this.state.node_name; -+ if (fullReset) { -+ current_node = DB_CONFIG; -+ } -+ -+ treeData[4].children = suffixData; // suffixes node -+ this.setState(() => ({ -+ nodes: treeData, -+ node_name: current_node, -+ }), this.loadAttrs); -+ }) -+ .fail(err => { -+ // Handle backend get-tree failure gracefully - let current_node = this.state.node_name; - if (fullReset) { - current_node = DB_CONFIG; -diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx -index ad48d1f87..91a8e3e37 100644 ---- a/src/cockpit/389-console/src/monitor.jsx -+++ b/src/cockpit/389-console/src/monitor.jsx -@@ -200,6 +200,84 @@ export class Monitor extends React.Component { - } - - loadSuffixTree(fullReset) { -+ const basicData = [ -+ { -+ name: _("Server Statistics"), -+ icon: , -+ id: "server-monitor", -+ type: "server", -+ }, -+ { -+ name: _("Replication"), -+ icon: , -+ id: "replication-monitor", -+ type: "replication", -+ defaultExpanded: true, -+ children: [ -+ { -+ name: _("Synchronization Report"), -+ icon: , -+ id: "sync-report", -+ item: "sync-report", -+ type: "repl-mon", -+ }, -+ { -+ name: _("Log Analysis"), -+ icon: , -+ id: "log-analysis", -+ item: "log-analysis", -+ type: "repl-mon", -+ } -+ ], -+ }, -+ { -+ name: _("Database"), -+ icon: , -+ id: "database-monitor", -+ type: "database", -+ children: [], // Will be populated with treeData on success -+ defaultExpanded: true, -+ }, -+ { -+ name: _("Logging"), -+ icon: , -+ id: "log-monitor", -+ defaultExpanded: true, -+ children: [ -+ { -+ name: _("Access Log"), -+ icon: , -+ id: "access-log-monitor", -+ type: "log", -+ }, -+ { -+ name: _("Audit Log"), -+ icon: , -+ id: "audit-log-monitor", -+ type: "log", -+ }, -+ { -+ name: _("Audit Failure Log"), -+ icon: , -+ id: "auditfail-log-monitor", -+ type: "log", -+ }, -+ { -+ name: _("Errors Log"), -+ icon: , -+ id: "error-log-monitor", -+ type: "log", -+ }, -+ { -+ name: _("Security Log"), -+ icon: , -+ id: "security-log-monitor", -+ type: "log", -+ }, -+ ] -+ }, -+ ]; -+ - const cmd = [ - "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", - "backend", "get-tree", -@@ -210,83 +288,7 @@ export class Monitor extends React.Component { - .done(content => { - const treeData = JSON.parse(content); - this.processTree(treeData); -- const basicData = [ -- { -- name: _("Server Statistics"), -- icon: , -- id: "server-monitor", -- type: "server", -- }, -- { -- name: _("Replication"), -- icon: , -- id: "replication-monitor", -- type: "replication", -- defaultExpanded: true, -- children: [ -- { -- name: _("Synchronization Report"), -- icon: , -- id: "sync-report", -- item: "sync-report", -- type: "repl-mon", -- }, -- { -- name: _("Log Analysis"), -- icon: , -- id: "log-analysis", -- item: "log-analysis", -- type: "repl-mon", -- } -- ], -- }, -- { -- name: _("Database"), -- icon: , -- id: "database-monitor", -- type: "database", -- children: [], -- defaultExpanded: true, -- }, -- { -- name: _("Logging"), -- icon: , -- id: "log-monitor", -- defaultExpanded: true, -- children: [ -- { -- name: _("Access Log"), -- icon: , -- id: "access-log-monitor", -- type: "log", -- }, -- { -- name: _("Audit Log"), -- icon: , -- id: "audit-log-monitor", -- type: "log", -- }, -- { -- name: _("Audit Failure Log"), -- icon: , -- id: "auditfail-log-monitor", -- type: "log", -- }, -- { -- name: _("Errors Log"), -- icon: , -- id: "error-log-monitor", -- type: "log", -- }, -- { -- name: _("Security Log"), -- icon: , -- id: "security-log-monitor", -- type: "log", -- }, -- ] -- }, -- ]; -+ - let current_node = this.state.node_name; - let type = this.state.node_type; - if (fullReset) { -@@ -296,6 +298,22 @@ export class Monitor extends React.Component { - basicData[2].children = treeData; // database node - this.processReplSuffixes(basicData[1].children); - -+ this.setState(() => ({ -+ nodes: basicData, -+ node_name: current_node, -+ node_type: type, -+ }), this.update_tree_nodes); -+ }) -+ .fail(err => { -+ // Handle backend get-tree failure gracefully -+ let current_node = this.state.node_name; -+ let type = this.state.node_type; -+ if (fullReset) { -+ current_node = "server-monitor"; -+ type = "server"; -+ } -+ this.processReplSuffixes(basicData[1].children); -+ - this.setState(() => ({ - nodes: basicData, - node_name: current_node, -diff --git a/src/cockpit/389-console/src/replication.jsx b/src/cockpit/389-console/src/replication.jsx -index fa492fd2a..aa535bfc7 100644 ---- a/src/cockpit/389-console/src/replication.jsx -+++ b/src/cockpit/389-console/src/replication.jsx -@@ -177,6 +177,16 @@ export class Replication extends React.Component { - loaded: false - }); - -+ const basicData = [ -+ { -+ name: _("Suffixes"), -+ icon: , -+ id: "repl-suffixes", -+ children: [], -+ defaultExpanded: true -+ } -+ ]; -+ - const cmd = [ - "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", - "backend", "get-tree", -@@ -199,15 +209,7 @@ export class Replication extends React.Component { - } - } - } -- const basicData = [ -- { -- name: _("Suffixes"), -- icon: , -- id: "repl-suffixes", -- children: [], -- defaultExpanded: true -- } -- ]; -+ - let current_node = this.state.node_name; - let current_type = this.state.node_type; - let replicated = this.state.node_replicated; -@@ -258,6 +260,19 @@ export class Replication extends React.Component { - } - - basicData[0].children = treeData; -+ this.setState({ -+ nodes: basicData, -+ node_name: current_node, -+ node_type: current_type, -+ node_replicated: replicated, -+ }, () => { this.update_tree_nodes() }); -+ }) -+ .fail(err => { -+ // Handle backend get-tree failure gracefully -+ let current_node = this.state.node_name; -+ let current_type = this.state.node_type; -+ let replicated = this.state.node_replicated; -+ - this.setState({ - nodes: basicData, - node_name: current_node, -@@ -905,18 +920,18 @@ export class Replication extends React.Component { - disableTree: false - }); - }); -- }) -- .fail(err => { -- const errMsg = JSON.parse(err); -- this.props.addNotification( -- "error", -- cockpit.format(_("Error loading replication agreements configuration - $0"), errMsg.desc) -- ); -- this.setState({ -- suffixLoading: false, -- disableTree: false -+ }) -+ .fail(err => { -+ const errMsg = JSON.parse(err); -+ this.props.addNotification( -+ "error", -+ cockpit.format(_("Error loading replication agreements configuration - $0"), errMsg.desc) -+ ); -+ this.setState({ -+ suffixLoading: false, -+ disableTree: false -+ }); - }); -- }); - }) - .fail(err => { - // changelog failure -diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py -index 1319fa0cd..5bff61c58 100644 ---- a/src/lib389/lib389/backend.py -+++ b/src/lib389/lib389/backend.py -@@ -694,24 +694,32 @@ class Backend(DSLdapObject): - parent_suffix = properties.pop('parent', False) - - # Okay, now try to make the backend. -- super(Backend, self).create(dn, properties, basedn) -+ backend_obj = super(Backend, self).create(dn, properties, basedn) - - # We check if the mapping tree exists in create, so do this *after* - if create_mapping_tree is True: -- properties = { -+ mapping_tree_properties = { - 'cn': self._nprops_stash['nsslapd-suffix'], - 'nsslapd-state': 'backend', - 'nsslapd-backend': self._nprops_stash['cn'], - } - if parent_suffix: - # This is a subsuffix, set the parent suffix -- properties['nsslapd-parent-suffix'] = parent_suffix -- self._mts.create(properties=properties) -+ mapping_tree_properties['nsslapd-parent-suffix'] = parent_suffix -+ -+ try: -+ self._mts.create(properties=mapping_tree_properties) -+ except Exception as e: -+ try: -+ backend_obj.delete() -+ except Exception as cleanup_error: -+ self._instance.log.error(f"Failed to cleanup backend after mapping tree creation failure: {cleanup_error}") -+ raise e - - # We can't create the sample entries unless a mapping tree was installed. - if sample_entries is not False and create_mapping_tree is True: - self.create_sample_entries(sample_entries) -- return self -+ return backend_obj - - def delete(self): - """Deletes the backend, it's mapping tree and all related indices. --- -2.49.0 - diff --git a/0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch b/0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch deleted file mode 100644 index 5d62b75..0000000 --- a/0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0a7fe7c6e18759459499f468443ded4313ebdeab Mon Sep 17 00:00:00 2001 -From: Alexander Bokovoy -Date: Wed, 9 Jul 2025 12:08:09 +0300 -Subject: [PATCH] Issue 6857 - uiduniq: allow specifying match rules in the - filter - -Allow uniqueness plugin to work with attributes where uniqueness should -be enforced using different matching rule than the one defined for the -attribute itself. - -Since uniqueness plugin configuration can contain multiple attributes, -add matching rule right to the attribute as it is used in the LDAP rule -(e.g. 'attribute:caseIgnoreMatch:' to force 'attribute' to be searched -with case-insensitive matching rule instead of the original matching -rule. - -Fixes: https://github.com/389ds/389-ds-base/issues/6857 - -Signed-off-by: Alexander Bokovoy ---- - ldap/servers/plugins/uiduniq/uid.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c -index 053af4f9d..887e79d78 100644 ---- a/ldap/servers/plugins/uiduniq/uid.c -+++ b/ldap/servers/plugins/uiduniq/uid.c -@@ -1030,7 +1030,14 @@ preop_add(Slapi_PBlock *pb) - } - - for (i = 0; attrNames && attrNames[i]; i++) { -+ char *attr_match = strchr(attrNames[i], ':'); -+ if (attr_match != NULL) { -+ attr_match[0] = '\0'; -+ } - err = slapi_entry_attr_find(e, attrNames[i], &attr); -+ if (attr_match != NULL) { -+ attr_match[0] = ':'; -+ } - if (!err) { - /* - * Passed all the requirements - this is an operation we --- -2.49.0 - diff --git a/0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch b/0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch deleted file mode 100644 index f519007..0000000 --- a/0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch +++ /dev/null @@ -1,1201 +0,0 @@ -From b28b00ee5169cfb00414bc9bcca67f88432ad567 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Thu, 10 Jul 2025 11:53:12 -0700 -Subject: [PATCH] Issue 6756 - CLI, UI - Properly handle disabled NDN cache - (#6757) - -Description: Fix the db_monitor function in monitor.py to check if -nsslapd-ndn-cache-enabled is off and conditionally include NDN cache -statistics only when enabled. - -Update dbMonitor.jsx components to detect when NDN cache is disabled and -conditionally render NDN cache tabs, charts, and related content with proper -fallback display when disabled. - -Add test_ndn_cache_disabled to verify both JSON and non-JSON output formats -correctly handle when NDN cache is turned off and on. - -Fixes: https://github.com/389ds/389-ds-base/issues/6756 - -Reviewed by: @mreynolds389 (Thanks!) ---- - dirsrvtests/tests/suites/clu/dbmon_test.py | 90 +++ - src/cockpit/389-console/src/database.jsx | 4 +- - .../src/lib/database/databaseConfig.jsx | 48 +- - .../389-console/src/lib/monitor/dbMonitor.jsx | 735 ++++++++++-------- - src/lib389/lib389/cli_conf/monitor.py | 77 +- - 5 files changed, 580 insertions(+), 374 deletions(-) - -diff --git a/dirsrvtests/tests/suites/clu/dbmon_test.py b/dirsrvtests/tests/suites/clu/dbmon_test.py -index 4a82eb0ef..b04ee67c9 100644 ---- a/dirsrvtests/tests/suites/clu/dbmon_test.py -+++ b/dirsrvtests/tests/suites/clu/dbmon_test.py -@@ -11,6 +11,7 @@ import subprocess - import pytest - import json - import glob -+import re - - from lib389.tasks import * - from lib389.utils import * -@@ -274,6 +275,95 @@ def test_dbmon_mp_pagesize(topology_st): - assert real_free_percentage == dbmon_free_percentage - - -+def test_ndn_cache_disabled(topology_st): -+ """Test dbmon output when ndn-cache-enabled is turned off -+ -+ :id: 760e217c-70e8-4767-b504-dda7ba2e1f64 -+ :setup: Standalone instance -+ :steps: -+ 1. Run dbmon with nsslapd-ndn-cache-enabled=on (default) -+ 2. Verify NDN cache stats are present in the output -+ 3. Set nsslapd-ndn-cache-enabled=off and restart -+ 4. Run dbmon again and verify NDN cache stats are not present -+ 5. Set nsslapd-ndn-cache-enabled=on and restart -+ 6. Run dbmon again and verify NDN cache stats are back -+ :expectedresults: -+ 1. Success -+ 2. Should display NDN cache data -+ 3. Success -+ 4. Should not display NDN cache data -+ 5. Success -+ 6. Should display NDN cache data -+ """ -+ inst = topology_st.standalone -+ args = FakeArgs() -+ args.backends = None -+ args.indexes = False -+ args.json = True -+ lc = LogCapture() -+ -+ log.info("Testing with NDN cache enabled (default)") -+ db_monitor(inst, DEFAULT_SUFFIX, lc.log, args) -+ db_mon_as_str = "".join((str(rec) for rec in lc.outputs)) -+ db_mon_as_str = re.sub("^[^{]*{", "{", db_mon_as_str)[:-2] -+ db_mon = json.loads(db_mon_as_str) -+ -+ assert 'ndncache' in db_mon -+ assert 'hit_ratio' in db_mon['ndncache'] -+ lc.flush() -+ -+ log.info("Setting nsslapd-ndn-cache-enabled to OFF") -+ inst.config.set('nsslapd-ndn-cache-enabled', 'off') -+ inst.restart() -+ -+ log.info("Testing with NDN cache disabled") -+ db_monitor(inst, DEFAULT_SUFFIX, lc.log, args) -+ db_mon_as_str = "".join((str(rec) for rec in lc.outputs)) -+ db_mon_as_str = re.sub("^[^{]*{", "{", db_mon_as_str)[:-2] -+ db_mon = json.loads(db_mon_as_str) -+ -+ assert 'ndncache' not in db_mon -+ lc.flush() -+ -+ log.info("Setting nsslapd-ndn-cache-enabled to ON") -+ inst.config.set('nsslapd-ndn-cache-enabled', 'on') -+ inst.restart() -+ -+ log.info("Testing with NDN cache re-enabled") -+ db_monitor(inst, DEFAULT_SUFFIX, lc.log, args) -+ db_mon_as_str = "".join((str(rec) for rec in lc.outputs)) -+ db_mon_as_str = re.sub("^[^{]*{", "{", db_mon_as_str)[:-2] -+ db_mon = json.loads(db_mon_as_str) -+ -+ assert 'ndncache' in db_mon -+ assert 'hit_ratio' in db_mon['ndncache'] -+ lc.flush() -+ -+ args.json = False -+ -+ log.info("Testing with NDN cache enabled - non-JSON output") -+ db_monitor(inst, DEFAULT_SUFFIX, lc.log, args) -+ output = "".join((str(rec) for rec in lc.outputs)) -+ -+ assert "Normalized DN Cache:" in output -+ assert "Cache Hit Ratio:" in output -+ lc.flush() -+ -+ log.info("Setting nsslapd-ndn-cache-enabled to OFF") -+ inst.config.set('nsslapd-ndn-cache-enabled', 'off') -+ inst.restart() -+ -+ log.info("Testing with NDN cache disabled - non-JSON output") -+ db_monitor(inst, DEFAULT_SUFFIX, lc.log, args) -+ output = "".join((str(rec) for rec in lc.outputs)) -+ -+ assert "Normalized DN Cache:" not in output -+ lc.flush() -+ -+ inst.config.set('nsslapd-ndn-cache-enabled', 'on') -+ inst.restart() -+ -+ - if __name__ == '__main__': - # Run isolated - # -s for DEBUG mode -diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx -index 276125dfc..86b642b92 100644 ---- a/src/cockpit/389-console/src/database.jsx -+++ b/src/cockpit/389-console/src/database.jsx -@@ -198,7 +198,7 @@ export class Database extends React.Component { - }); - const cmd = [ - "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", -- "config", "get", "nsslapd-ndn-cache-max-size" -+ "config", "get", "nsslapd-ndn-cache-max-size", "nsslapd-ndn-cache-enabled" - ]; - log_cmd("loadNDN", "Load NDN cache size", cmd); - cockpit -@@ -206,10 +206,12 @@ export class Database extends React.Component { - .done(content => { - const config = JSON.parse(content); - const attrs = config.attrs; -+ const ndn_cache_enabled = attrs['nsslapd-ndn-cache-enabled'][0] === "on"; - this.setState(prevState => ({ - globalDBConfig: { - ...prevState.globalDBConfig, - ndncachemaxsize: attrs['nsslapd-ndn-cache-max-size'][0], -+ ndn_cache_enabled: ndn_cache_enabled, - }, - configUpdated: 0, - loaded: true, -diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx -index 4c7fce706..adb8227d7 100644 ---- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx -+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx -@@ -2,12 +2,16 @@ import cockpit from "cockpit"; - import React from "react"; - import { log_cmd } from "../tools.jsx"; - import { -+ Alert, - Button, - Checkbox, -+ Form, - Grid, - GridItem, -+ Hr, - NumberInput, - Spinner, -+ Switch, - Tab, - Tabs, - TabTitleText, -@@ -852,12 +856,29 @@ export class GlobalDatabaseConfig extends React.Component { - - {_("NDN Cache")}}> -
-+ -+ {this.props.data.ndn_cache_enabled === false && ( -+ -+ -+ {_("The Normalized DN Cache is currently disabled. To enable it, go to Server Settings → Tuning & Limits and enable 'Normalized DN Cache', then restart the server for the changes to take effect.")} -+ -+ -+ )} -+ - - -- {_("Normalized DN Cache Max Size")} -+ {_("Normalized DN Cache Max Size") } - - - - - -@@ -1470,7 +1491,7 @@ export class GlobalDatabaseConfigMDB extends React.Component { - {_("Database Size")}}> -
- - -@@ -1641,6 +1662,23 @@ export class GlobalDatabaseConfigMDB extends React.Component { - - {_("NDN Cache")}}> -
-+ -+ {this.props.data.ndn_cache_enabled === false && ( -+ -+ -+ {_("The Normalized DN Cache is currently disabled. To enable it, go to Server Settings → Tuning & Limits and enable 'Normalized DN Cache', then restart the server for the changes to take effect.")} -+ -+ -+ )} -+ - 0; -+ let ndn_chart_data = this.state.ndnCacheList; -+ let ndn_util_chart_data = this.state.ndnCacheUtilList; -+ -+ // Only build NDN cache chart data if NDN cache is enabled -+ if (ndn_cache_enabled) { -+ const ndnratio = config.attrs.normalizeddncachehitratio[0]; -+ ndn_chart_data = this.state.ndnCacheList; -+ ndn_chart_data.shift(); -+ ndn_chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(ndnratio) }); -+ -+ // Build up the NDN Cache Util chart data -+ ndn_util_chart_data = this.state.ndnCacheUtilList; -+ const currNDNSize = parseInt(config.attrs.currentnormalizeddncachesize[0]); -+ const maxNDNSize = parseInt(config.attrs.maxnormalizeddncachesize[0]); -+ const ndn_utilization = (currNDNSize / maxNDNSize) * 100; -+ ndn_util_chart_data.shift(); -+ ndn_util_chart_data.push({ name: _("Cache Utilization"), x: ndnCount.toString(), y: parseInt(ndn_utilization) }); -+ } - - this.setState({ - data: config.attrs, -@@ -157,7 +167,8 @@ export class DatabaseMonitor extends React.Component { - ndnCacheList: ndn_chart_data, - ndnCacheUtilList: ndn_util_chart_data, - count, -- ndnCount -+ ndnCount, -+ ndn_cache_enabled - }); - }) - .fail(() => { -@@ -197,13 +208,20 @@ export class DatabaseMonitor extends React.Component { - - if (!this.state.loading) { - dbcachehit = parseInt(this.state.data.dbcachehitratio[0]); -- ndncachehit = parseInt(this.state.data.normalizeddncachehitratio[0]); -- ndncachemax = parseInt(this.state.data.maxnormalizeddncachesize[0]); -- ndncachecurr = parseInt(this.state.data.currentnormalizeddncachesize[0]); -- utilratio = Math.round((ndncachecurr / ndncachemax) * 100); -- if (utilratio === 0) { -- // Just round up to 1 -- utilratio = 1; -+ -+ // Check if NDN cache is enabled -+ const ndn_cache_enabled = this.state.data.normalizeddncachehitratio && -+ this.state.data.normalizeddncachehitratio.length > 0; -+ -+ if (ndn_cache_enabled) { -+ ndncachehit = parseInt(this.state.data.normalizeddncachehitratio[0]); -+ ndncachemax = parseInt(this.state.data.maxnormalizeddncachesize[0]); -+ ndncachecurr = parseInt(this.state.data.currentnormalizeddncachesize[0]); -+ utilratio = Math.round((ndncachecurr / ndncachemax) * 100); -+ if (utilratio === 0) { -+ // Just round up to 1 -+ utilratio = 1; -+ } - } - - // Database cache -@@ -214,119 +232,131 @@ export class DatabaseMonitor extends React.Component { - } else { - chartColor = ChartThemeColor.purple; - } -- // NDN cache ratio -- if (ndncachehit > 89) { -- ndnChartColor = ChartThemeColor.green; -- } else if (ndncachehit > 74) { -- ndnChartColor = ChartThemeColor.orange; -- } else { -- ndnChartColor = ChartThemeColor.purple; -- } -- // NDN cache utilization -- if (utilratio > 95) { -- ndnUtilColor = ChartThemeColor.purple; -- } else if (utilratio > 90) { -- ndnUtilColor = ChartThemeColor.orange; -- } else { -- ndnUtilColor = ChartThemeColor.green; -+ -+ // NDN cache colors only if enabled -+ if (ndn_cache_enabled) { -+ // NDN cache ratio -+ if (ndncachehit > 89) { -+ ndnChartColor = ChartThemeColor.green; -+ } else if (ndncachehit > 74) { -+ ndnChartColor = ChartThemeColor.orange; -+ } else { -+ ndnChartColor = ChartThemeColor.purple; -+ } -+ // NDN cache utilization -+ if (utilratio > 95) { -+ ndnUtilColor = ChartThemeColor.purple; -+ } else if (utilratio > 90) { -+ ndnUtilColor = ChartThemeColor.orange; -+ } else { -+ ndnUtilColor = ChartThemeColor.green; -+ } - } - -- content = ( -- -- {_("Database Cache")}}> --
-- -- --
--
-- -- -- {_("Cache Hit Ratio")} -- -- -- -- -- {dbcachehit}% -- -- --
--
-- `${datum.name}: ${datum.y}`} constrainToVisibleArea />} -- height={200} -- maxDomain={{ y: 100 }} -- minDomain={{ y: 0 }} -- padding={{ -- bottom: 30, -- left: 40, -- top: 10, -- right: 10, -- }} -- width={500} -- themeColor={chartColor} -- > -- -- -- -- -- -- --
-+ // Create tabs based on what caches are available -+ const tabs = []; -+ -+ // Database Cache tab is always available -+ tabs.push( -+ {_("Database Cache")}}> -+
-+ -+ -+
-+
-+ -+ -+ {_("Cache Hit Ratio")} -+ -+ -+ -+ -+ {dbcachehit}% -+ -+ -
-- -- --
-+
-+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} -+ height={200} -+ maxDomain={{ y: 100 }} -+ minDomain={{ y: 0 }} -+ padding={{ -+ bottom: 30, -+ left: 40, -+ top: 10, -+ right: 10, -+ }} -+ width={500} -+ themeColor={chartColor} -+ > -+ -+ -+ -+ -+ -+ -+
-+
-+ -+ -+
-+ -+ -+ -+ {_("Database Cache Hit Ratio:")} -+ -+ -+ {this.state.data.dbcachehitratio}% -+ -+ -+ {_("Database Cache Tries:")} -+ -+ -+ {numToCommas(this.state.data.dbcachetries)} -+ -+ -+ {_("Database Cache Hits:")} -+ -+ -+ {numToCommas(this.state.data.dbcachehits)} -+ -+ -+ {_("Cache Pages Read:")} -+ -+ -+ {numToCommas(this.state.data.dbcachepagein)} -+ -+ -+ {_("Cache Pages Written:")} -+ -+ -+ {numToCommas(this.state.data.dbcachepageout)} -+ -+ -+ {_("Read-Only Page Evictions:")} -+ -+ -+ {numToCommas(this.state.data.dbcacheroevict)} -+ -+ -+ {_("Read-Write Page Evictions:")} -+ -+ -+ {numToCommas(this.state.data.dbcacherwevict)} -+ -+ -+ -+ ); - -- -- -- {_("Database Cache Hit Ratio:")} -- -- -- {this.state.data.dbcachehitratio}% -- -- -- {_("Database Cache Tries:")} -- -- -- {numToCommas(this.state.data.dbcachetries)} -- -- -- {_("Database Cache Hits:")} -- -- -- {numToCommas(this.state.data.dbcachehits)} -- -- -- {_("Cache Pages Read:")} -- -- -- {numToCommas(this.state.data.dbcachepagein)} -- -- -- {_("Cache Pages Written:")} -- -- -- {numToCommas(this.state.data.dbcachepageout)} -- -- -- {_("Read-Only Page Evictions:")} -- -- -- {numToCommas(this.state.data.dbcacheroevict)} -- -- -- {_("Read-Write Page Evictions:")} -- -- -- {numToCommas(this.state.data.dbcacherwevict)} -- -- -- -- {_("Normalized DN Cache")}}> -+ // Only add NDN Cache tab if NDN cache is enabled -+ if (ndn_cache_enabled) { -+ tabs.push( -+ {_("Normalized DN Cache")}}> -
- - -@@ -487,6 +517,12 @@ export class DatabaseMonitor extends React.Component { - -
-
-+ ); -+ } -+ -+ content = ( -+ -+ {tabs} - - ); - } -@@ -533,7 +569,8 @@ export class DatabaseMonitorMDB extends React.Component { - ndnCount: 5, - dbCacheList: [], - ndnCacheList: [], -- ndnCacheUtilList: [] -+ ndnCacheUtilList: [], -+ ndn_cache_enabled: false - }; - - // Toggle currently active tab -@@ -585,6 +622,7 @@ export class DatabaseMonitorMDB extends React.Component { - { name: "", x: "4", y: 0 }, - { name: "", x: "5", y: 0 }, - ], -+ ndn_cache_enabled: false - }); - } - -@@ -605,19 +643,28 @@ export class DatabaseMonitorMDB extends React.Component { - count = 1; - } - -- // Build up the NDN Cache chart data -- const ndnratio = config.attrs.normalizeddncachehitratio[0]; -- const ndn_chart_data = this.state.ndnCacheList; -- ndn_chart_data.shift(); -- ndn_chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(ndnratio) }); -- -- // Build up the DB Cache Util chart data -- const ndn_util_chart_data = this.state.ndnCacheUtilList; -- const currNDNSize = parseInt(config.attrs.currentnormalizeddncachesize[0]); -- const maxNDNSize = parseInt(config.attrs.maxnormalizeddncachesize[0]); -- const ndn_utilization = (currNDNSize / maxNDNSize) * 100; -- ndn_util_chart_data.shift(); -- ndn_util_chart_data.push({ name: _("Cache Utilization"), x: ndnCount.toString(), y: parseInt(ndn_utilization) }); -+ // Check if NDN cache is enabled -+ const ndn_cache_enabled = config.attrs.normalizeddncachehitratio && -+ config.attrs.normalizeddncachehitratio.length > 0; -+ let ndn_chart_data = this.state.ndnCacheList; -+ let ndn_util_chart_data = this.state.ndnCacheUtilList; -+ -+ // Only build NDN cache chart data if NDN cache is enabled -+ if (ndn_cache_enabled) { -+ // Build up the NDN Cache chart data -+ const ndnratio = config.attrs.normalizeddncachehitratio[0]; -+ ndn_chart_data = this.state.ndnCacheList; -+ ndn_chart_data.shift(); -+ ndn_chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(ndnratio) }); -+ -+ // Build up the DB Cache Util chart data -+ ndn_util_chart_data = this.state.ndnCacheUtilList; -+ const currNDNSize = parseInt(config.attrs.currentnormalizeddncachesize[0]); -+ const maxNDNSize = parseInt(config.attrs.maxnormalizeddncachesize[0]); -+ const ndn_utilization = (currNDNSize / maxNDNSize) * 100; -+ ndn_util_chart_data.shift(); -+ ndn_util_chart_data.push({ name: _("Cache Utilization"), x: ndnCount.toString(), y: parseInt(ndn_utilization) }); -+ } - - this.setState({ - data: config.attrs, -@@ -625,7 +672,8 @@ export class DatabaseMonitorMDB extends React.Component { - ndnCacheList: ndn_chart_data, - ndnCacheUtilList: ndn_util_chart_data, - count, -- ndnCount -+ ndnCount, -+ ndn_cache_enabled - }); - }) - .fail(() => { -@@ -662,197 +710,214 @@ export class DatabaseMonitorMDB extends React.Component { - ); - - if (!this.state.loading) { -- ndncachehit = parseInt(this.state.data.normalizeddncachehitratio[0]); -- ndncachemax = parseInt(this.state.data.maxnormalizeddncachesize[0]); -- ndncachecurr = parseInt(this.state.data.currentnormalizeddncachesize[0]); -- utilratio = Math.round((ndncachecurr / ndncachemax) * 100); -- if (utilratio === 0) { -- // Just round up to 1 -- utilratio = 1; -- } -- -- // NDN cache ratio -- if (ndncachehit > 89) { -- ndnChartColor = ChartThemeColor.green; -- } else if (ndncachehit > 74) { -- ndnChartColor = ChartThemeColor.orange; -- } else { -- ndnChartColor = ChartThemeColor.purple; -- } -- // NDN cache utilization -- if (utilratio > 95) { -- ndnUtilColor = ChartThemeColor.purple; -- } else if (utilratio > 90) { -- ndnUtilColor = ChartThemeColor.orange; -- } else { -- ndnUtilColor = ChartThemeColor.green; -- } -- -- content = ( -- -- {_("Normalized DN Cache")}}> --
-- -- -- -- --
--
-- -- -- {_("Cache Hit Ratio")} -- -- -- -- -- {ndncachehit}% -- -- --
--
-- `${datum.name}: ${datum.y}`} constrainToVisibleArea />} -- height={200} -- maxDomain={{ y: 100 }} -- minDomain={{ y: 0 }} -- padding={{ -- bottom: 40, -- left: 60, -- top: 10, -- right: 15, -- }} -- width={350} -- themeColor={ndnChartColor} -- > -- -- -- -- -- -- --
--
--
--
--
-- -- -- --
--
-- -- -- {_("Cache Utilization")} -- -- -- -- -- {utilratio}% -- -- -- -- -- {_("Cached DN's")} -- -- -- {numToCommas(this.state.data.currentnormalizeddncachecount[0])} -+ // Check if NDN cache is enabled -+ const ndn_cache_enabled = this.state.data.normalizeddncachehitratio && -+ this.state.data.normalizeddncachehitratio.length > 0; -+ -+ if (ndn_cache_enabled) { -+ ndncachehit = parseInt(this.state.data.normalizeddncachehitratio[0]); -+ ndncachemax = parseInt(this.state.data.maxnormalizeddncachesize[0]); -+ ndncachecurr = parseInt(this.state.data.currentnormalizeddncachesize[0]); -+ utilratio = Math.round((ndncachecurr / ndncachemax) * 100); -+ if (utilratio === 0) { -+ // Just round up to 1 -+ utilratio = 1; -+ } -+ -+ // NDN cache ratio -+ if (ndncachehit > 89) { -+ ndnChartColor = ChartThemeColor.green; -+ } else if (ndncachehit > 74) { -+ ndnChartColor = ChartThemeColor.orange; -+ } else { -+ ndnChartColor = ChartThemeColor.purple; -+ } -+ // NDN cache utilization -+ if (utilratio > 95) { -+ ndnUtilColor = ChartThemeColor.purple; -+ } else if (utilratio > 90) { -+ ndnUtilColor = ChartThemeColor.orange; -+ } else { -+ ndnUtilColor = ChartThemeColor.green; -+ } -+ -+ content = ( -+ -+ {_("Normalized DN Cache")}}> -+
-+ -+ -+ -+ -+
-+
-+ -+ -+ {_("Cache Hit Ratio")} -+ -+ -+ -+ -+ {ndncachehit}% -+ -+ -+
-+
-+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} -+ height={200} -+ maxDomain={{ y: 100 }} -+ minDomain={{ y: 0 }} -+ padding={{ -+ bottom: 40, -+ left: 60, -+ top: 10, -+ right: 15, -+ }} -+ width={350} -+ themeColor={ndnChartColor} -+ > -+ -+ -+ -+ -+ -+ -+
-
--
-- `${datum.name}: ${datum.y}`} constrainToVisibleArea />} -- height={200} -- maxDomain={{ y: 100 }} -- minDomain={{ y: 0 }} -- padding={{ -- bottom: 40, -- left: 60, -- top: 10, -- right: 15, -- }} -- width={350} -- themeColor={ndnUtilColor} -- > -- -- -- -- -- -- -+ -+ -+ -+ -+ -+ -+
-+
-+ -+ -+ {_("Cache Utilization")} -+ -+ -+ -+ -+ {utilratio}% -+ -+ -+ -+ -+ {_("Cached DN's")} -+ -+ -+ {numToCommas(this.state.data.currentnormalizeddncachecount[0])} -+
-+
-+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} -+ height={200} -+ maxDomain={{ y: 100 }} -+ minDomain={{ y: 0 }} -+ padding={{ -+ bottom: 40, -+ left: 60, -+ top: 10, -+ right: 15, -+ }} -+ width={350} -+ themeColor={ndnUtilColor} -+ > -+ -+ -+ -+ -+ -+ -+
-
--
--
--
--
--
-- -- -- -- {_("NDN Cache Hit Ratio:")} -- -- -- {this.state.data.normalizeddncachehitratio}% -- -- -- {_("NDN Cache Max Size:")} -- -- -- {displayBytes(this.state.data.maxnormalizeddncachesize)} -- -- -- {_("NDN Cache Tries:")} -- -- -- {numToCommas(this.state.data.normalizeddncachetries)} -- -- -- {_("NDN Current Cache Size:")} -- -- -- {displayBytes(this.state.data.currentnormalizeddncachesize)} -- -- -- {_("NDN Cache Hits:")} -- -- -- {numToCommas(this.state.data.normalizeddncachehits)} -- -- -- {_("NDN Cache DN Count:")} -- -- -- {numToCommas(this.state.data.currentnormalizeddncachecount)} -- -- -- {_("NDN Cache Evictions:")} -- -- -- {numToCommas(this.state.data.normalizeddncacheevictions)} -- -- -- {_("NDN Cache Thread Size:")} -- -- -- {numToCommas(this.state.data.normalizeddncachethreadsize)} -- -- -- {_("NDN Cache Thread Slots:")} -- -- -- {numToCommas(this.state.data.normalizeddncachethreadslots)} -- -- --
--
--
-- ); -+ -+ -+ -+ -+ -+ -+ -+ {_("NDN Cache Hit Ratio:")} -+ -+ -+ {this.state.data.normalizeddncachehitratio}% -+ -+ -+ {_("NDN Cache Max Size:")} -+ -+ -+ {displayBytes(this.state.data.maxnormalizeddncachesize)} -+ -+ -+ {_("NDN Cache Tries:")} -+ -+ -+ {numToCommas(this.state.data.normalizeddncachetries)} -+ -+ -+ {_("NDN Current Cache Size:")} -+ -+ -+ {displayBytes(this.state.data.currentnormalizeddncachesize)} -+ -+ -+ {_("NDN Cache Hits:")} -+ -+ -+ {numToCommas(this.state.data.normalizeddncachehits)} -+ -+ -+ {_("NDN Cache DN Count:")} -+ -+ -+ {numToCommas(this.state.data.currentnormalizeddncachecount)} -+ -+ -+ {_("NDN Cache Evictions:")} -+ -+ -+ {numToCommas(this.state.data.normalizeddncacheevictions)} -+ -+ -+ {_("NDN Cache Thread Size:")} -+ -+ -+ {numToCommas(this.state.data.normalizeddncachethreadsize)} -+ -+ -+ {_("NDN Cache Thread Slots:")} -+ -+ -+ {numToCommas(this.state.data.normalizeddncachethreadslots)} -+ -+ -+
-+ -+ -+ ); -+ } else { -+ // No NDN cache available -+ content = ( -+
-+ -+ -+ {_("Normalized DN Cache is disabled")} -+ -+ -+
-+ ); -+ } - } - - return ( -diff --git a/src/lib389/lib389/cli_conf/monitor.py b/src/lib389/lib389/cli_conf/monitor.py -index b01796549..c7f9322d1 100644 ---- a/src/lib389/lib389/cli_conf/monitor.py -+++ b/src/lib389/lib389/cli_conf/monitor.py -@@ -129,6 +129,14 @@ def db_monitor(inst, basedn, log, args): - # Gather the global DB stats - report_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - ldbm_mon = ldbm_monitor.get_status() -+ ndn_cache_enabled = inst.config.get_attr_val_utf8('nsslapd-ndn-cache-enabled') == 'on' -+ -+ # Build global cache stats -+ result = { -+ 'date': report_time, -+ 'backends': {}, -+ } -+ - if ldbm_monitor.inst_db_impl == DB_IMPL_BDB: - dbcachesize = int(ldbm_mon['nsslapd-db-cache-size-bytes'][0]) - # Warning: there are two different page sizes associated with bdb: -@@ -153,32 +161,6 @@ def db_monitor(inst, basedn, log, args): - dbcachefree = max(int(dbcachesize - (pagesize * dbpages)), 0) - dbcachefreeratio = dbcachefree/dbcachesize - -- ndnratio = ldbm_mon['normalizeddncachehitratio'][0] -- ndncursize = int(ldbm_mon['currentnormalizeddncachesize'][0]) -- ndnmaxsize = int(ldbm_mon['maxnormalizeddncachesize'][0]) -- ndncount = ldbm_mon['currentnormalizeddncachecount'][0] -- ndnevictions = ldbm_mon['normalizeddncacheevictions'][0] -- if ndncursize > ndnmaxsize: -- ndnfree = 0 -- ndnfreeratio = 0 -- else: -- ndnfree = ndnmaxsize - ndncursize -- ndnfreeratio = "{:.1f}".format(ndnfree / ndnmaxsize * 100) -- -- # Build global cache stats -- result = { -- 'date': report_time, -- 'ndncache': { -- 'hit_ratio': ndnratio, -- 'free': convert_bytes(str(ndnfree)), -- 'free_percentage': ndnfreeratio, -- 'count': ndncount, -- 'evictions': ndnevictions -- }, -- 'backends': {}, -- } -- -- if ldbm_monitor.inst_db_impl == DB_IMPL_BDB: - result['dbcache'] = { - 'hit_ratio': dbhitratio, - 'free': convert_bytes(str(dbcachefree)), -@@ -188,6 +170,32 @@ def db_monitor(inst, basedn, log, args): - 'pageout': dbcachepageout - } - -+ # Add NDN cache stats only if enabled -+ if ndn_cache_enabled: -+ try: -+ ndnratio = ldbm_mon['normalizeddncachehitratio'][0] -+ ndncursize = int(ldbm_mon['currentnormalizeddncachesize'][0]) -+ ndnmaxsize = int(ldbm_mon['maxnormalizeddncachesize'][0]) -+ ndncount = ldbm_mon['currentnormalizeddncachecount'][0] -+ ndnevictions = ldbm_mon['normalizeddncacheevictions'][0] -+ if ndncursize > ndnmaxsize: -+ ndnfree = 0 -+ ndnfreeratio = 0 -+ else: -+ ndnfree = ndnmaxsize - ndncursize -+ ndnfreeratio = "{:.1f}".format(ndnfree / ndnmaxsize * 100) -+ -+ result['ndncache'] = { -+ 'hit_ratio': ndnratio, -+ 'free': convert_bytes(str(ndnfree)), -+ 'free_percentage': ndnfreeratio, -+ 'count': ndncount, -+ 'evictions': ndnevictions -+ } -+ # In case, the user enabled NDN cache but still have not restarted the instance -+ except IndexError: -+ ndn_cache_enabled = False -+ - # Build the backend results - for be in backend_objs: - be_name = be.rdn -@@ -277,13 +285,16 @@ def db_monitor(inst, basedn, log, args): - log.info(" - Pages In: {}".format(result['dbcache']['pagein'])) - log.info(" - Pages Out: {}".format(result['dbcache']['pageout'])) - log.info("") -- log.info("Normalized DN Cache:") -- log.info(" - Cache Hit Ratio: {}%".format(result['ndncache']['hit_ratio'])) -- log.info(" - Free Space: {}".format(result['ndncache']['free'])) -- log.info(" - Free Percentage: {}%".format(result['ndncache']['free_percentage'])) -- log.info(" - DN Count: {}".format(result['ndncache']['count'])) -- log.info(" - Evictions: {}".format(result['ndncache']['evictions'])) -- log.info("") -+ -+ if ndn_cache_enabled: -+ log.info("Normalized DN Cache:") -+ log.info(" - Cache Hit Ratio: {}%".format(result['ndncache']['hit_ratio'])) -+ log.info(" - Free Space: {}".format(result['ndncache']['free'])) -+ log.info(" - Free Percentage: {}%".format(result['ndncache']['free_percentage'])) -+ log.info(" - DN Count: {}".format(result['ndncache']['count'])) -+ log.info(" - Evictions: {}".format(result['ndncache']['evictions'])) -+ log.info("") -+ - log.info("Backends:") - for be_name, attr_dict in result['backends'].items(): - log.info(f" - {attr_dict['suffix']} ({be_name}):") --- -2.49.0 - diff --git a/0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch b/0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch deleted file mode 100644 index 09cc802..0000000 --- a/0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch +++ /dev/null @@ -1,399 +0,0 @@ -From 5198da59d622dbc39afe2ece9c6f40f4fb249d52 Mon Sep 17 00:00:00 2001 -From: Mark Reynolds -Date: Wed, 9 Jul 2025 14:18:50 -0400 -Subject: [PATCH] Issue 6859 - str2filter is not fully applying matching rules - -Description: - -When we have an extended filter, one with a MR applied, it is ignored during -internal searches: - - "(cn:CaseExactMatch:=Value)" - -For internal searches we use str2filter() and it doesn't fully apply extended -search filter matching rules - -Also needed to update attr uniqueness plugin to apply this change for mod -operations (previously only Adds were correctly handling these attribute -filters) - -Relates: https://github.com/389ds/389-ds-base/issues/6857 -Relates: https://github.com/389ds/389-ds-base/issues/6859 - -Reviewed by: spichugi & tbordaz(Thanks!!) ---- - .../tests/suites/plugins/attruniq_test.py | 295 +++++++++++++++++- - ldap/servers/plugins/uiduniq/uid.c | 7 + - ldap/servers/slapd/plugin_mr.c | 2 +- - ldap/servers/slapd/str2filter.c | 8 + - 4 files changed, 309 insertions(+), 3 deletions(-) - -diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py -index b190e0ec1..b338f405f 100644 ---- a/dirsrvtests/tests/suites/plugins/attruniq_test.py -+++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py -@@ -1,5 +1,5 @@ - # --- BEGIN COPYRIGHT BLOCK --- --# Copyright (C) 2021 Red Hat, Inc. -+# Copyright (C) 2025 Red Hat, Inc. - # All rights reserved. - # - # License: GPL (version 3 or any later version). -@@ -80,4 +80,295 @@ def test_modrdn_attr_uniqueness(topology_st): - log.debug(excinfo.value) - - log.debug('Move user2 to group1') -- user2.rename(f'uid={user2.rdn}', group1.dn) -\ No newline at end of file -+ -+ user2.rename(f'uid={user2.rdn}', group1.dn) -+ -+ # Cleanup for next test -+ user1.delete() -+ user2.delete() -+ attruniq.disable() -+ attruniq.delete() -+ -+ -+def test_multiple_attr_uniqueness(topology_st): -+ """ Test that attribute uniqueness works properly with multiple attributes -+ -+ :id: c49aa5c1-7e65-45fd-b064-55e0b815e9bc -+ :setup: Standalone instance -+ :steps: -+ 1. Setup attribute uniqueness plugin to ensure uniqueness of attributes 'mail' and 'mailAlternateAddress' -+ 2. Add user with unique 'mail=non-uniq@value.net' and 'mailAlternateAddress=alt-mail@value.net' -+ 3. Try adding another user with 'mail=non-uniq@value.net' -+ 4. Try adding another user with 'mailAlternateAddress=alt-mail@value.net' -+ 5. Try adding another user with 'mail=alt-mail@value.net' -+ 6. Try adding another user with 'mailAlternateAddress=non-uniq@value.net' -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Should raise CONSTRAINT_VIOLATION -+ 4. Should raise CONSTRAINT_VIOLATION -+ 5. Should raise CONSTRAINT_VIOLATION -+ 6. Should raise CONSTRAINT_VIOLATION -+ """ -+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config") -+ -+ try: -+ log.debug(f'Setup PLUGIN_ATTR_UNIQUENESS plugin for {MAIL_ATTR_VALUE} attribute for the group2') -+ attruniq.create(properties={'cn': 'attruniq'}) -+ 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 -+ -+ topology_st.standalone.restart() -+ -+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX) -+ -+ testuser1 = users.create_test_user(100,100) -+ testuser1.add('objectclass', 'extensibleObject') -+ testuser1.add('mail', MAIL_ATTR_VALUE) -+ testuser1.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT) -+ -+ testuser2 = users.create_test_user(200, 200) -+ testuser2.add('objectclass', 'extensibleObject') -+ -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ testuser2.add('mail', MAIL_ATTR_VALUE) -+ -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT) -+ -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ testuser2.add('mail', MAIL_ATTR_VALUE_ALT) -+ -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE) -+ -+ # Cleanup -+ testuser1.delete() -+ testuser2.delete() -+ attruniq.disable() -+ attruniq.delete() -+ -+ -+def test_exclude_subtrees(topology_st): -+ """ Test attribute uniqueness with exclude scope -+ -+ :id: 43d29a60-40e1-4ebd-b897-6ef9f20e9f27 -+ :setup: Standalone instance -+ :steps: -+ 1. Setup and enable attribute uniqueness plugin for telephonenumber unique attribute -+ 2. Create subtrees and test users -+ 3. Add a unique attribute to a user within uniqueness scope -+ 4. Add exclude subtree -+ 5. Try to add existing value attribute to an entry within uniqueness scope -+ 6. Try to add existing value attribute to an entry within exclude scope -+ 7. Remove the attribute from affected entries -+ 8. Add a unique attribute to a user within exclude scope -+ 9. Try to add existing value attribute to an entry within uniqueness scope -+ 10. Try to add existing value attribute to another entry within uniqueness scope -+ 11. Remove the attribute from affected entries -+ 12. Add another exclude subtree -+ 13. Add a unique attribute to a user within uniqueness scope -+ 14. Try to add existing value attribute to an entry within uniqueness scope -+ 15. Try to add existing value attribute to an entry within exclude scope -+ 16. Try to add existing value attribute to an entry within another exclude scope -+ 17. Clean up entries -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Should raise CONSTRAINT_VIOLATION -+ 6. Success -+ 7. Success -+ 8. Success -+ 9. Success -+ 10. Should raise CONSTRAINT_VIOLATION -+ 11. Success -+ 12. Success -+ 13. Success -+ 14. Should raise CONSTRAINT_VIOLATION -+ 15. Success -+ 16. Success -+ 17. Success -+ """ -+ 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('telephonenumber') -+ attruniq.add_unique_subtree(DEFAULT_SUFFIX) -+ attruniq.enable_all_subtrees() -+ attruniq.enable() -+ topology_st.standalone.restart() -+ -+ log.info('Create subtrees container') -+ containers = nsContainers(topology_st.standalone, DEFAULT_SUFFIX) -+ cont1 = containers.create(properties={'cn': EXCLUDED_CONTAINER_CN}) -+ cont2 = containers.create(properties={'cn': EXCLUDED_BIS_CONTAINER_CN}) -+ cont3 = containers.create(properties={'cn': ENFORCED_CONTAINER_CN}) -+ -+ log.info('Create test users') -+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, -+ rdn='cn={}'.format(ENFORCED_CONTAINER_CN)) -+ users_excluded = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, -+ rdn='cn={}'.format(EXCLUDED_CONTAINER_CN)) -+ users_excluded2 = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, -+ rdn='cn={}'.format(EXCLUDED_BIS_CONTAINER_CN)) -+ -+ user1 = users.create(properties={'cn': USER_1_CN, -+ 'uid': USER_1_CN, -+ 'sn': USER_1_CN, -+ 'uidNumber': '1', -+ 'gidNumber': '11', -+ 'homeDirectory': '/home/{}'.format(USER_1_CN)}) -+ user2 = users.create(properties={'cn': USER_2_CN, -+ 'uid': USER_2_CN, -+ 'sn': USER_2_CN, -+ 'uidNumber': '2', -+ 'gidNumber': '22', -+ 'homeDirectory': '/home/{}'.format(USER_2_CN)}) -+ user3 = users_excluded.create(properties={'cn': USER_3_CN, -+ 'uid': USER_3_CN, -+ 'sn': USER_3_CN, -+ 'uidNumber': '3', -+ 'gidNumber': '33', -+ 'homeDirectory': '/home/{}'.format(USER_3_CN)}) -+ user4 = users_excluded2.create(properties={'cn': USER_4_CN, -+ 'uid': USER_4_CN, -+ 'sn': USER_4_CN, -+ 'uidNumber': '4', -+ 'gidNumber': '44', -+ 'homeDirectory': '/home/{}'.format(USER_4_CN)}) -+ -+ UNIQUE_VALUE = '1234' -+ -+ try: -+ log.info('Create user with unique attribute') -+ user1.add('telephonenumber', UNIQUE_VALUE) -+ assert user1.present('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Add exclude subtree') -+ attruniq.add_exclude_subtree(EXCLUDED_CONTAINER_DN) -+ topology_st.standalone.restart() -+ -+ log.info('Verify an already used attribute value cannot be added within the same subtree') -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ user2.add('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Verify an entry with same attribute value can be added within exclude subtree') -+ user3.add('telephonenumber', UNIQUE_VALUE) -+ assert user3.present('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Cleanup unique attribute values') -+ user1.remove_all('telephonenumber') -+ user3.remove_all('telephonenumber') -+ -+ log.info('Add a unique value to an entry in excluded scope') -+ user3.add('telephonenumber', UNIQUE_VALUE) -+ assert user3.present('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Verify the same value can be added to an entry within uniqueness scope') -+ user1.add('telephonenumber', UNIQUE_VALUE) -+ assert user1.present('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Verify that yet another same value cannot be added to another entry within uniqueness scope') -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ user2.add('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Cleanup unique attribute values') -+ user1.remove_all('telephonenumber') -+ user3.remove_all('telephonenumber') -+ -+ log.info('Add another exclude subtree') -+ attruniq.add_exclude_subtree(EXCLUDED_BIS_CONTAINER_DN) -+ topology_st.standalone.restart() -+ -+ user1.add('telephonenumber', UNIQUE_VALUE) -+ log.info('Verify an already used attribute value cannot be added within the same subtree') -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ user2.add('telephonenumber', UNIQUE_VALUE) -+ -+ log.info('Verify an already used attribute can be added to an entry in exclude scope') -+ user3.add('telephonenumber', UNIQUE_VALUE) -+ assert user3.present('telephonenumber', UNIQUE_VALUE) -+ user4.add('telephonenumber', UNIQUE_VALUE) -+ assert user4.present('telephonenumber', UNIQUE_VALUE) -+ -+ finally: -+ log.info('Clean up users, containers and attribute uniqueness plugin') -+ user1.delete() -+ user2.delete() -+ user3.delete() -+ user4.delete() -+ cont1.delete() -+ cont2.delete() -+ cont3.delete() -+ attruniq.disable() -+ attruniq.delete() -+ -+ -+def test_matchingrule_attr(topology_st): -+ """ Test list extension MR attribute. Check for "cn" using CES (versus it -+ being defined as CIS) -+ -+ :id: 5cde4342-6fa3-4225-b23d-0af918981075 -+ :setup: Standalone instance -+ :steps: -+ 1. Setup and enable attribute uniqueness plugin to use CN attribute -+ with a matching rule of CaseExactMatch. -+ 2. Add user with CN value is lowercase -+ 3. Add second user with same lowercase CN which should be rejected -+ 4. Add second user with same CN value but with mixed case -+ 5. Modify second user replacing CN value to lc which should be rejected -+ -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Success -+ """ -+ -+ inst = topology_st.standalone -+ -+ attruniq = AttributeUniquenessPlugin(inst, -+ dn="cn=attribute uniqueness,cn=plugins,cn=config") -+ attruniq.add_unique_attribute('cn:CaseExactMatch:') -+ attruniq.enable_all_subtrees() -+ attruniq.enable() -+ inst.restart() -+ -+ users = UserAccounts(inst, DEFAULT_SUFFIX) -+ users.create(properties={'cn': "common_name", -+ 'uid': "uid_name", -+ 'sn': "uid_name", -+ 'uidNumber': '1', -+ 'gidNumber': '11', -+ 'homeDirectory': '/home/uid_name'}) -+ -+ log.info('Add entry with the exact CN value which should be rejected') -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ users.create(properties={'cn': "common_name", -+ 'uid': "uid_name2", -+ 'sn': "uid_name2", -+ 'uidNumber': '11', -+ 'gidNumber': '111', -+ 'homeDirectory': '/home/uid_name2'}) -+ -+ log.info('Add entry with the mixed case CN value which should be allowed') -+ user = users.create(properties={'cn': "Common_Name", -+ 'uid': "uid_name2", -+ 'sn': "uid_name2", -+ 'uidNumber': '11', -+ 'gidNumber': '111', -+ 'homeDirectory': '/home/uid_name2'}) -+ -+ log.info('Mod entry with exact case CN value which should be rejected') -+ with pytest.raises(ldap.CONSTRAINT_VIOLATION): -+ user.replace('cn', 'common_name') -diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c -index 887e79d78..fdb1404a0 100644 ---- a/ldap/servers/plugins/uiduniq/uid.c -+++ b/ldap/servers/plugins/uiduniq/uid.c -@@ -1178,6 +1178,10 @@ preop_modify(Slapi_PBlock *pb) - for (; mods && *mods; mods++) { - mod = *mods; - for (i = 0; attrNames && attrNames[i]; i++) { -+ char *attr_match = strchr(attrNames[i], ':'); -+ if (attr_match != NULL) { -+ attr_match[0] = '\0'; -+ } - if ((slapi_attr_type_cmp(mod->mod_type, attrNames[i], 1) == 0) && /* mod contains target attr */ - (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */ - (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */ -@@ -1186,6 +1190,9 @@ preop_modify(Slapi_PBlock *pb) - { - addMod(&checkmods, &checkmodsCapacity, &modcount, mod); - } -+ if (attr_match != NULL) { -+ attr_match[0] = ':'; -+ } - } - } - if (modcount == 0) { -diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c -index b262820c5..67051a5ff 100644 ---- a/ldap/servers/slapd/plugin_mr.c -+++ b/ldap/servers/slapd/plugin_mr.c -@@ -626,7 +626,7 @@ attempt_mr_filter_create(mr_filter_t *f, struct slapdplugin *mrp, Slapi_PBlock * - int rc; - IFP mrf_create = NULL; - f->mrf_match = NULL; -- pblock_init(pb); -+ slapi_pblock_init(pb); - if (!(rc = slapi_pblock_set(pb, SLAPI_PLUGIN, mrp)) && - !(rc = slapi_pblock_get(pb, SLAPI_PLUGIN_MR_FILTER_CREATE_FN, &mrf_create)) && - mrf_create != NULL && -diff --git a/ldap/servers/slapd/str2filter.c b/ldap/servers/slapd/str2filter.c -index 9fdc500f7..5620b7439 100644 ---- a/ldap/servers/slapd/str2filter.c -+++ b/ldap/servers/slapd/str2filter.c -@@ -344,6 +344,14 @@ str2simple(char *str, int unescape_filter) - return NULL; /* error */ - } else { - f->f_choice = LDAP_FILTER_EXTENDED; -+ if (f->f_mr_oid) { -+ /* apply the MR indexers */ -+ rc = plugin_mr_filter_create(&f->f_mr); -+ if (rc) { -+ slapi_filter_free(f, 1); -+ return NULL; /* error */ -+ } -+ } - } - } else if (str_find_star(value) == NULL) { - f->f_choice = LDAP_FILTER_EQUALITY; --- -2.49.0 - diff --git a/0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch b/0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch deleted file mode 100644 index cc717a3..0000000 --- a/0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch +++ /dev/null @@ -1,163 +0,0 @@ -From 406563c136d78235751e34a3c7e22ccaf114f754 Mon Sep 17 00:00:00 2001 -From: Mark Reynolds -Date: Tue, 15 Jul 2025 17:56:18 -0400 -Subject: [PATCH] Issue 6872 - compressed log rotation creates files with world - readable permission - -Description: - -When compressing a log file, first create the empty file using open() -so we can set the correct permissions right from the start. gzopen() -always uses permission 644 and that is not safe. So after creating it -with open(), with the correct permissions, then pass the FD to gzdopen() -and write the compressed content. - -relates: https://github.com/389ds/389-ds-base/issues/6872 - -Reviewed by: progier(Thanks!) ---- - .../logging/logging_compression_test.py | 15 ++++++++-- - ldap/servers/slapd/log.c | 28 +++++++++++++------ - ldap/servers/slapd/schema.c | 2 +- - 3 files changed, 33 insertions(+), 12 deletions(-) - -diff --git a/dirsrvtests/tests/suites/logging/logging_compression_test.py b/dirsrvtests/tests/suites/logging/logging_compression_test.py -index e30874cc0..3a987d62c 100644 ---- a/dirsrvtests/tests/suites/logging/logging_compression_test.py -+++ b/dirsrvtests/tests/suites/logging/logging_compression_test.py -@@ -1,5 +1,5 @@ - # --- BEGIN COPYRIGHT BLOCK --- --# Copyright (C) 2022 Red Hat, Inc. -+# Copyright (C) 2025 Red Hat, Inc. - # All rights reserved. - # - # License: GPL (version 3 or any later version). -@@ -22,12 +22,21 @@ log = logging.getLogger(__name__) - - pytestmark = pytest.mark.tier1 - -+ - def log_rotated_count(log_type, log_dir, check_compressed=False): -- # Check if the log was rotated -+ """ -+ Check if the log was rotated and has the correct permissions -+ """ - log_file = f'{log_dir}/{log_type}.2*' - if check_compressed: - log_file += ".gz" -- return len(glob.glob(log_file)) -+ log_files = glob.glob(log_file) -+ for logf in log_files: -+ # Check permissions -+ st = os.stat(logf) -+ assert oct(st.st_mode) == '0o100600' # 0600 -+ -+ return len(log_files) - - - def update_and_sleep(inst, suffix, sleep=True): -diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c -index a018ca2d5..178d29b89 100644 ---- a/ldap/servers/slapd/log.c -+++ b/ldap/servers/slapd/log.c -@@ -172,17 +172,28 @@ get_syslog_loglevel(int loglevel) - } - - static int --compress_log_file(char *log_name) -+compress_log_file(char *log_name, int32_t mode) - { - char gzip_log[BUFSIZ] = {0}; - char buf[LOG_CHUNK] = {0}; - size_t bytes_read = 0; - gzFile outfile = NULL; - FILE *source = NULL; -+ int fd = 0; - - PR_snprintf(gzip_log, sizeof(gzip_log), "%s.gz", log_name); -- if ((outfile = gzopen(gzip_log,"wb")) == NULL) { -- /* Failed to open new gzip file */ -+ -+ /* -+ * Try to open the file as we may have an incorrect path. We also need to -+ * set the permissions using open() as gzopen() creates the file with -+ * 644 permissions (world readable - bad). So we create an empty file with -+ * the correct permissions, then we pass the FD to gzdopen() to write the -+ * compressed content. -+ */ -+ if ((fd = open(gzip_log, O_WRONLY|O_CREAT|O_TRUNC, mode)) >= 0) { -+ /* FIle successfully created, now pass the FD to gzdopen() */ -+ outfile = gzdopen(fd, "ab"); -+ } else { - return -1; - } - -@@ -191,6 +202,7 @@ compress_log_file(char *log_name) - gzclose(outfile); - return -1; - } -+ - bytes_read = fread(buf, 1, LOG_CHUNK, source); - while (bytes_read > 0) { - int bytes_written = gzwrite(outfile, buf, bytes_read); -@@ -3291,7 +3303,7 @@ log__open_accesslogfile(int logfile_state, int locked) - return LOG_UNABLE_TO_OPENFILE; - } - } else if (loginfo.log_access_compress) { -- if (compress_log_file(newfile) != 0) { -+ if (compress_log_file(newfile, loginfo.log_access_mode) != 0) { - slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile", - "failed to compress rotated access log (%s)\n", - newfile); -@@ -3455,7 +3467,7 @@ log__open_securitylogfile(int logfile_state, int locked) - return LOG_UNABLE_TO_OPENFILE; - } - } else if (loginfo.log_security_compress) { -- if (compress_log_file(newfile) != 0) { -+ if (compress_log_file(newfile, loginfo.log_security_mode) != 0) { - slapi_log_err(SLAPI_LOG_ERR, "log__open_securitylogfile", - "failed to compress rotated security audit log (%s)\n", - newfile); -@@ -6172,7 +6184,7 @@ log__open_errorlogfile(int logfile_state, int locked) - return LOG_UNABLE_TO_OPENFILE; - } - } else if (loginfo.log_error_compress) { -- if (compress_log_file(newfile) != 0) { -+ if (compress_log_file(newfile, loginfo.log_error_mode) != 0) { - PR_snprintf(buffer, sizeof(buffer), "Failed to compress errors log file (%s)\n", newfile); - log__error_emergency(buffer, 1, 1); - } else { -@@ -6355,7 +6367,7 @@ log__open_auditlogfile(int logfile_state, int locked) - return LOG_UNABLE_TO_OPENFILE; - } - } else if (loginfo.log_audit_compress) { -- if (compress_log_file(newfile) != 0) { -+ if (compress_log_file(newfile, loginfo.log_audit_mode) != 0) { - slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile", - "failed to compress rotated audit log (%s)\n", - newfile); -@@ -6514,7 +6526,7 @@ log__open_auditfaillogfile(int logfile_state, int locked) - return LOG_UNABLE_TO_OPENFILE; - } - } else if (loginfo.log_auditfail_compress) { -- if (compress_log_file(newfile) != 0) { -+ if (compress_log_file(newfile, loginfo.log_auditfail_mode) != 0) { - slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile", - "failed to compress rotated auditfail log (%s)\n", - newfile); -diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c -index a8e6b1210..9ef4ee4bf 100644 ---- a/ldap/servers/slapd/schema.c -+++ b/ldap/servers/slapd/schema.c -@@ -903,7 +903,7 @@ oc_check_allowed_sv(Slapi_PBlock *pb, Slapi_Entry *e, const char *type, struct o - - if (pb) { - PR_snprintf(errtext, sizeof(errtext), -- "attribute \"%s\" not allowed\n", -+ "attribute \"%s\" not allowed", - escape_string(type, ebuf)); - slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, errtext); - } --- -2.49.0 - diff --git a/0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch b/0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch deleted file mode 100644 index 8dbfc90..0000000 --- a/0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 9b8b23f6d46f16fbc1784b26cfc04dd6b4fa94e1 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Fri, 18 Jul 2025 18:50:33 -0700 -Subject: [PATCH] Issue 6878 - Prevent repeated disconnect logs during shutdown - (#6879) - -Description: Avoid logging non-active initialized connections via CONN in disconnect_server_nomutex_ext by adding a check to skip invalid conn=0 with invalid sockets, preventing excessive repeated messages. - -Update ds_logs_test.py by adding test_no_repeated_disconnect_messages to verify the fix. - -Fixes: https://github.com/389ds/389-ds-base/issues/6878 - -Reviewed by: @mreynolds389 (Thanks!) ---- - .../tests/suites/ds_logs/ds_logs_test.py | 51 ++++++++++++++++++- - ldap/servers/slapd/connection.c | 15 +++--- - 2 files changed, 59 insertions(+), 7 deletions(-) - -diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py -index 2c22347bb..b86c72687 100644 ---- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py -+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py -@@ -24,7 +24,7 @@ from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, Aut - from lib389.idm.user import UserAccounts, UserAccount - from lib389.idm.group import Groups - from lib389.idm.organizationalunit import OrganizationalUnits --from lib389._constants import DEFAULT_SUFFIX, LOG_ACCESS_LEVEL, PASSWORD -+from lib389._constants import DEFAULT_SUFFIX, LOG_ACCESS_LEVEL, PASSWORD, ErrorLog - from lib389.utils import ds_is_older, ds_is_newer - from lib389.config import RSA - from lib389.dseldif import DSEldif -@@ -1435,6 +1435,55 @@ def test_errorlog_buffering(topology_st, request): - assert inst.ds_error_log.match(".*slapd_daemon - slapd started.*") - - -+def test_no_repeated_disconnect_messages(topology_st): -+ """Test that there are no repeated "Not setting conn 0 to be disconnected: socket is invalid" messages on restart -+ -+ :id: 72b5e1ce-2db8-458f-b2cd-0a0b6525f51f -+ :setup: Standalone Instance -+ :steps: -+ 1. Set error log level to CONNECTION -+ 2. Clear existing error logs -+ 3. Restart the server with 30 second timeout -+ 4. Check error log for repeated disconnect messages -+ 5. Verify there are no more than 10 occurrences of the disconnect message -+ :expectedresults: -+ 1. Error log level should be set successfully -+ 2. Error logs should be cleared -+ 3. Server should restart successfully within 30 seconds -+ 4. Error log should be accessible -+ 5. There should be no more than 10 repeated disconnect messages -+ """ -+ -+ inst = topology_st.standalone -+ -+ log.info('Set error log level to CONNECTION') -+ inst.config.loglevel([ErrorLog.CONNECT]) -+ current_level = inst.config.get_attr_val_int('nsslapd-errorlog-level') -+ log.info(f'Error log level set to: {current_level}') -+ -+ log.info('Clear existing error logs') -+ inst.deleteErrorLogs() -+ -+ log.info('Restart the server with 30 second timeout') -+ inst.restart(timeout=30) -+ -+ log.info('Check error log for repeated disconnect messages') -+ disconnect_message = "Not setting conn 0 to be disconnected: socket is invalid" -+ -+ # Count occurrences of the disconnect message -+ error_log_lines = inst.ds_error_log.readlines() -+ disconnect_count = 0 -+ -+ for line in error_log_lines: -+ if disconnect_message in line: -+ disconnect_count += 1 -+ -+ log.info(f'Found {disconnect_count} occurrences of disconnect message') -+ -+ log.info('Verify there are no more than 10 occurrences') -+ assert disconnect_count <= 10, f"Found {disconnect_count} repeated disconnect messages, expected <= 10" -+ -+ - if __name__ == '__main__': - # Run isolated - # -s for DEBUG mode -diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c -index bb4fcd77f..2967de15b 100644 ---- a/ldap/servers/slapd/connection.c -+++ b/ldap/servers/slapd/connection.c -@@ -2465,12 +2465,15 @@ disconnect_server_nomutex_ext(Connection *conn, PRUint64 opconnid, int opid, PRE - } - - } else { -- slapi_log_err(SLAPI_LOG_CONNS, "disconnect_server_nomutex_ext", -- "Not setting conn %d to be disconnected: %s\n", -- conn->c_sd, -- (conn->c_sd == SLAPD_INVALID_SOCKET) ? "socket is invalid" : -- ((conn->c_connid != opconnid) ? "conn id does not match op conn id" : -- ((conn->c_flags & CONN_FLAG_CLOSING) ? "conn is closing" : "unknown"))); -+ /* We avoid logging an invalid conn=0 connection as it is not a real connection. */ -+ if (!(conn->c_sd == SLAPD_INVALID_SOCKET && conn->c_connid == 0)) { -+ slapi_log_err(SLAPI_LOG_CONNS, "disconnect_server_nomutex_ext", -+ "Not setting conn %d to be disconnected: %s\n", -+ conn->c_sd, -+ (conn->c_sd == SLAPD_INVALID_SOCKET) ? "socket is invalid" : -+ ((conn->c_connid != opconnid) ? "conn id does not match op conn id" : -+ ((conn->c_flags & CONN_FLAG_CLOSING) ? "conn is closing" : "unknown"))); -+ } - } - } - --- -2.49.0 - diff --git a/0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch b/0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch deleted file mode 100644 index 64f23ea..0000000 --- a/0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch +++ /dev/null @@ -1,67 +0,0 @@ -From fef4875a9c3d67ef424a1fb1698ae011152735b1 Mon Sep 17 00:00:00 2001 -From: Anuar Beisembayev <111912342+abeisemb@users.noreply.github.com> -Date: Wed, 23 Jul 2025 23:48:11 -0400 -Subject: [PATCH] Issue 6772 - dsconf - Replicas with the "consumer" role allow - for viewing and modification of their changelog. (#6773) - -dsconf currently allows users to set and retrieve changelogs in consumer replicas, which do not have officially supported changelogs. This can lead to undefined behavior and confusion. -This commit prints a warning message if the user tries to interact with a changelog on a consumer replica. - -Resolves: https://github.com/389ds/389-ds-base/issues/6772 - -Reviewed by: @droideck ---- - src/lib389/lib389/cli_conf/replication.py | 23 +++++++++++++++++++++++ - 1 file changed, 23 insertions(+) - -diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py -index 6f77f34ca..a18bf83ca 100644 ---- a/src/lib389/lib389/cli_conf/replication.py -+++ b/src/lib389/lib389/cli_conf/replication.py -@@ -686,6 +686,9 @@ def set_per_backend_cl(inst, basedn, log, args): - replace_list = [] - did_something = False - -+ if (is_replica_role_consumer(inst, suffix)): -+ log.info("Warning: Changelogs are not supported for consumer replicas. You may run into undefined behavior.") -+ - if args.encrypt: - cl.replace('nsslapd-encryptionalgorithm', 'AES') - del args.encrypt -@@ -715,6 +718,10 @@ def set_per_backend_cl(inst, basedn, log, args): - # that means there is a changelog config entry per backend (aka suffix) - def get_per_backend_cl(inst, basedn, log, args): - suffix = args.suffix -+ -+ if (is_replica_role_consumer(inst, suffix)): -+ log.info("Warning: Changelogs are not supported for consumer replicas. You may run into undefined behavior.") -+ - cl = Changelog(inst, suffix) - if args and args.json: - log.info(cl.get_all_attrs_json()) -@@ -822,6 +829,22 @@ def del_repl_manager(inst, basedn, log, args): - - log.info("Successfully deleted replication manager: " + manager_dn) - -+def is_replica_role_consumer(inst, suffix): -+ """Helper function for get_per_backend_cl and set_per_backend_cl. -+ Makes sure the instance in question is not a consumer, which is a role that -+ does not support changelogs. -+ """ -+ replicas = Replicas(inst) -+ try: -+ replica = replicas.get(suffix) -+ role = replica.get_role() -+ except ldap.NO_SUCH_OBJECT: -+ raise ValueError(f"Backend \"{suffix}\" is not enabled for replication") -+ -+ if role == ReplicaRole.CONSUMER: -+ return True -+ else: -+ return False - - # - # Agreements --- -2.49.0 - diff --git a/0014-Issue-6893-Log-user-that-is-updated-during-password-.patch b/0014-Issue-6893-Log-user-that-is-updated-during-password-.patch deleted file mode 100644 index d75bc8d..0000000 --- a/0014-Issue-6893-Log-user-that-is-updated-during-password-.patch +++ /dev/null @@ -1,143 +0,0 @@ -From 4cb50f83397e6a5e14a9b75ed15f24189ee2792b Mon Sep 17 00:00:00 2001 -From: Mark Reynolds -Date: Mon, 21 Jul 2025 18:07:21 -0400 -Subject: [PATCH] Issue 6893 - Log user that is updated during password modify - extended operation - -Description: - -When a user's password is updated via an extended operation (password modify -plugin) we only log the bind DN and not what user was updated. While "internal -operation" logging will display the the user it should be logged by the default -logging level. - -Add access logging using "EXT_INFO" where we display the bind dn, target -dn, and message. - -Relates: https://github.com/389ds/389-ds-base/issues/6893 - -Reviewed by: spichugi & tbordaz(Thanks!!) ---- - ldap/servers/slapd/passwd_extop.c | 56 +++++++++++++++---------------- - 1 file changed, 28 insertions(+), 28 deletions(-) - -diff --git a/ldap/servers/slapd/passwd_extop.c b/ldap/servers/slapd/passwd_extop.c -index 4bb60afd6..0296d64fb 100644 ---- a/ldap/servers/slapd/passwd_extop.c -+++ b/ldap/servers/slapd/passwd_extop.c -@@ -465,12 +465,13 @@ passwd_modify_extop(Slapi_PBlock *pb) - BerElement *response_ber = NULL; - Slapi_Entry *targetEntry = NULL; - Connection *conn = NULL; -+ Operation *pb_op = NULL; - LDAPControl **req_controls = NULL; - LDAPControl **resp_controls = NULL; - passwdPolicy *pwpolicy = NULL; - Slapi_DN *target_sdn = NULL; - Slapi_Entry *referrals = NULL; -- /* Slapi_DN sdn; */ -+ Slapi_Backend *be = NULL; - - slapi_log_err(SLAPI_LOG_TRACE, "passwd_modify_extop", "=>\n"); - -@@ -647,7 +648,7 @@ parse_req_done: - } - dn = slapi_sdn_get_ndn(target_sdn); - if (dn == NULL || *dn == '\0') { -- /* Refuse the operation because they're bound anonymously */ -+ /* Invalid DN - refuse the operation */ - errMesg = "Invalid dn."; - rc = LDAP_INVALID_DN_SYNTAX; - goto free_and_return; -@@ -724,14 +725,19 @@ parse_req_done: - ber_free(response_ber, 1); - } - -- slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET, (void *)dn); -+ slapi_pblock_get(pb, SLAPI_OPERATION, &pb_op); -+ if (pb_op == NULL) { -+ slapi_log_err(SLAPI_LOG_ERR, "passwd_modify_extop", "pb_op is NULL\n"); -+ goto free_and_return; -+ } - -+ slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET, (void *)dn); - /* Now we have the DN, look for the entry */ - ret = passwd_modify_getEntry(dn, &targetEntry); - /* If we can't find the entry, then that's an error */ - if (ret) { - /* Couldn't find the entry, fail */ -- errMesg = "No such Entry exists."; -+ errMesg = "No such entry exists."; - rc = LDAP_NO_SUCH_OBJECT; - goto free_and_return; - } -@@ -742,30 +748,18 @@ parse_req_done: - leak any useful information to the client such as current password - wrong, etc. - */ -- Operation *pb_op = NULL; -- slapi_pblock_get(pb, SLAPI_OPERATION, &pb_op); -- if (pb_op == NULL) { -- slapi_log_err(SLAPI_LOG_ERR, "passwd_modify_extop", "pb_op is NULL\n"); -- goto free_and_return; -- } -- - operation_set_target_spec(pb_op, slapi_entry_get_sdn(targetEntry)); - slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &pb_op->o_isroot); - -- /* In order to perform the access control check , we need to select a backend (even though -- * we don't actually need it otherwise). -- */ -- { -- Slapi_Backend *be = NULL; -- -- be = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(targetEntry)); -- if (NULL == be) { -- errMesg = "Failed to find backend for target entry"; -- rc = LDAP_OPERATIONS_ERROR; -- goto free_and_return; -- } -- slapi_pblock_set(pb, SLAPI_BACKEND, be); -+ /* In order to perform the access control check, we need to select a backend (even though -+ * we don't actually need it otherwise). */ -+ be = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(targetEntry)); -+ if (NULL == be) { -+ errMesg = "Failed to find backend for target entry"; -+ rc = LDAP_NO_SUCH_OBJECT; -+ goto free_and_return; - } -+ slapi_pblock_set(pb, SLAPI_BACKEND, be); - - /* Check if the pwpolicy control is present */ - slapi_pblock_get(pb, SLAPI_PWPOLICY, &need_pwpolicy_ctrl); -@@ -797,10 +791,7 @@ parse_req_done: - /* Check if password policy allows users to change their passwords. We need to do - * this here since the normal modify code doesn't perform this check for - * internal operations. */ -- -- Connection *pb_conn; -- slapi_pblock_get(pb, SLAPI_CONNECTION, &pb_conn); -- if (!pb_op->o_isroot && !pb_conn->c_needpw && !pwpolicy->pw_change) { -+ if (!pb_op->o_isroot && !conn->c_needpw && !pwpolicy->pw_change) { - if (NULL == bindSDN) { - bindSDN = slapi_sdn_new_normdn_byref(bindDN); - } -@@ -848,6 +839,15 @@ free_and_return: - slapi_log_err(SLAPI_LOG_PLUGIN, "passwd_modify_extop", - "%s\n", errMesg ? errMesg : "success"); - -+ if (dn) { -+ /* Log the target ndn (if we have a target ndn) */ -+ slapi_log_access(LDAP_DEBUG_STATS, -+ "conn=%" PRIu64 " op=%d EXT_INFO name=\"passwd_modify_plugin\" bind_dn=\"%s\" target_dn=\"%s\" msg=\"%s\" rc=%d\n", -+ conn ? conn->c_connid : -1, pb_op ? pb_op->o_opid : -1, -+ bindDN ? bindDN : "", dn, -+ errMesg ? errMesg : "success", rc); -+ } -+ - if ((rc == LDAP_REFERRAL) && (referrals)) { - send_referrals_from_entry(pb, referrals); - } else { --- -2.49.0 - diff --git a/0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch b/0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch deleted file mode 100644 index 914d27b..0000000 --- a/0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch +++ /dev/null @@ -1,98 +0,0 @@ -From ffc3a81ed5852b7f1fbaed79b9b776af23d65b7c Mon Sep 17 00:00:00 2001 -From: Mark Reynolds -Date: Wed, 23 Jul 2025 19:35:32 -0400 -Subject: [PATCH] Issue 6895 - Crash if repl keep alive entry can not be - created - -Description: - -Heap use after free when logging that the replicaton keep-alive entry can not -be created. slapi_add_internal_pb() frees the slapi entry, then -we try and get the dn from the entry and we get a use-after-free crash. - -Relates: https://github.com/389ds/389-ds-base/issues/6895 - -Reviewed by: spichugi(Thanks!) ---- - ldap/servers/plugins/chainingdb/cb_config.c | 3 +-- - ldap/servers/plugins/posix-winsync/posix-winsync.c | 1 - - ldap/servers/plugins/replication/repl5_init.c | 3 --- - ldap/servers/plugins/replication/repl5_replica.c | 8 ++++---- - 4 files changed, 5 insertions(+), 10 deletions(-) - -diff --git a/ldap/servers/plugins/chainingdb/cb_config.c b/ldap/servers/plugins/chainingdb/cb_config.c -index 40a7088d7..24fa1bcb3 100644 ---- a/ldap/servers/plugins/chainingdb/cb_config.c -+++ b/ldap/servers/plugins/chainingdb/cb_config.c -@@ -44,8 +44,7 @@ cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *s - slapi_pblock_get(util_pb, SLAPI_PLUGIN_INTOP_RESULT, &res); - if (LDAP_SUCCESS != res && LDAP_ALREADY_EXISTS != res) { - slapi_log_err(SLAPI_LOG_ERR, CB_PLUGIN_SUBSYSTEM, -- "cb_config_add_dse_entries - Unable to add config entry (%s) to the DSE: %s\n", -- slapi_entry_get_dn(e), -+ "cb_config_add_dse_entries - Unable to add config entry to the DSE: %s\n", - ldap_err2string(res)); - rc = res; - slapi_pblock_destroy(util_pb); -diff --git a/ldap/servers/plugins/posix-winsync/posix-winsync.c b/ldap/servers/plugins/posix-winsync/posix-winsync.c -index 51a55b643..3a002bb70 100644 ---- a/ldap/servers/plugins/posix-winsync/posix-winsync.c -+++ b/ldap/servers/plugins/posix-winsync/posix-winsync.c -@@ -1626,7 +1626,6 @@ posix_winsync_end_update_cb(void *cbdata __attribute__((unused)), - "posix_winsync_end_update_cb: " - "add task entry\n"); - } -- /* slapi_entry_free(e_task); */ - slapi_pblock_destroy(pb); - pb = NULL; - posix_winsync_config_reset_MOFTaskCreated(); -diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c -index 8bc0b5372..5047fb8dc 100644 ---- a/ldap/servers/plugins/replication/repl5_init.c -+++ b/ldap/servers/plugins/replication/repl5_init.c -@@ -682,7 +682,6 @@ create_repl_schema_policy(void) - repl_schema_top, - ldap_err2string(return_value)); - rc = -1; -- slapi_entry_free(e); /* The entry was not consumed */ - goto done; - } - slapi_pblock_destroy(pb); -@@ -703,7 +702,6 @@ create_repl_schema_policy(void) - repl_schema_supplier, - ldap_err2string(return_value)); - rc = -1; -- slapi_entry_free(e); /* The entry was not consumed */ - goto done; - } - slapi_pblock_destroy(pb); -@@ -724,7 +722,6 @@ create_repl_schema_policy(void) - repl_schema_consumer, - ldap_err2string(return_value)); - rc = -1; -- slapi_entry_free(e); /* The entry was not consumed */ - goto done; - } - slapi_pblock_destroy(pb); -diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c -index 59062b46b..a97c807e9 100644 ---- a/ldap/servers/plugins/replication/repl5_replica.c -+++ b/ldap/servers/plugins/replication/repl5_replica.c -@@ -465,10 +465,10 @@ replica_subentry_create(const char *repl_root, ReplicaId rid) - if (return_value != LDAP_SUCCESS && - return_value != LDAP_ALREADY_EXISTS && - return_value != LDAP_REFERRAL /* CONSUMER */) { -- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_subentry_create - Unable to " -- "create replication keep alive entry %s: error %d - %s\n", -- slapi_entry_get_dn_const(e), -- return_value, ldap_err2string(return_value)); -+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_subentry_create - " -+ "Unable to create replication keep alive entry 'cn=%s %d,%s': error %d - %s\n", -+ KEEP_ALIVE_ENTRY, rid, repl_root, -+ return_value, ldap_err2string(return_value)); - rc = -1; - goto done; - } --- -2.49.0 - diff --git a/0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch b/0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch deleted file mode 100644 index 0e3949e..0000000 --- a/0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch +++ /dev/null @@ -1,352 +0,0 @@ -From 191634746fdcb7e26a154cd00a22324e02a10110 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Mon, 28 Jul 2025 10:50:26 -0700 -Subject: [PATCH] Issue 6250 - Add test for entryUSN overflow on failed add - operations (#6821) - -Description: Add comprehensive test to reproduce the entryUSN -overflow issue where failed attempts to add existing entries followed by -modify operations cause entryUSN values to underflow/overflow instead of -incrementing properly. - -Related: https://github.com/389ds/389-ds-base/issues/6250 - -Reviewed by: @tbordaz (Thanks!) ---- - .../suites/plugins/entryusn_overflow_test.py | 323 ++++++++++++++++++ - 1 file changed, 323 insertions(+) - create mode 100644 dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py - -diff --git a/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py -new file mode 100644 -index 000000000..a23d734ca ---- /dev/null -+++ b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py -@@ -0,0 +1,323 @@ -+# --- BEGIN COPYRIGHT BLOCK --- -+# Copyright (C) 2025 Red Hat, Inc. -+# All rights reserved. -+# -+# License: GPL (version 3 or any later version). -+# See LICENSE for details. -+# --- END COPYRIGHT BLOCK --- -+# -+import os -+import ldap -+import logging -+import pytest -+import time -+import random -+from lib389._constants import DEFAULT_SUFFIX -+from lib389.config import Config -+from lib389.plugins import USNPlugin -+from lib389.idm.user import UserAccounts -+from lib389.topologies import topology_st -+from lib389.rootdse import RootDSE -+ -+pytestmark = pytest.mark.tier2 -+ -+log = logging.getLogger(__name__) -+ -+# Test constants -+DEMO_USER_BASE_DN = "uid=demo_user,ou=people," + DEFAULT_SUFFIX -+TEST_USER_PREFIX = "Demo User" -+MAX_USN_64BIT = 18446744073709551615 # 2^64 - 1 -+ITERATIONS = 10 -+ADD_EXISTING_ENTRY_MAX_ATTEMPTS = 5 -+ -+ -+@pytest.fixture(scope="module") -+def setup_usn_test(topology_st, request): -+ """Setup USN plugin and test data for entryUSN overflow testing""" -+ -+ inst = topology_st.standalone -+ -+ log.info("Enable the USN plugin...") -+ plugin = USNPlugin(inst) -+ plugin.enable() -+ plugin.enable_global_mode() -+ -+ inst.restart() -+ -+ # Create initial test users -+ users = UserAccounts(inst, DEFAULT_SUFFIX) -+ created_users = [] -+ -+ log.info("Creating initial test users...") -+ for i in range(3): -+ user_props = { -+ 'uid': f'{TEST_USER_PREFIX}-{i}', -+ 'cn': f'{TEST_USER_PREFIX}-{i}', -+ 'sn': f'User{i}', -+ 'uidNumber': str(1000 + i), -+ 'gidNumber': str(1000 + i), -+ 'homeDirectory': f'/home/{TEST_USER_PREFIX}-{i}', -+ 'userPassword': 'password123' -+ } -+ try: -+ user = users.create(properties=user_props) -+ created_users.append(user) -+ log.info(f"Created user: {user.dn}") -+ except ldap.ALREADY_EXISTS: -+ log.info(f"User {user_props['uid']} already exists, skipping creation") -+ user = users.get(user_props['uid']) -+ created_users.append(user) -+ -+ def fin(): -+ log.info("Cleaning up test users...") -+ for user in created_users: -+ try: -+ user.delete() -+ except ldap.NO_SUCH_OBJECT: -+ pass -+ -+ request.addfinalizer(fin) -+ -+ return created_users -+ -+ -+def test_entryusn_overflow_on_add_existing_entries(topology_st, setup_usn_test): -+ """Test that reproduces entryUSN overflow when adding existing entries -+ -+ :id: a5a8c33d-82f3-4113-be2b-027de51791c8 -+ :setup: Standalone instance with USN plugin enabled and test users -+ :steps: -+ 1. Record initial entryUSN values for existing users -+ 2. Attempt to add existing entries multiple times (should fail) -+ 3. Perform modify operations on the entries -+ 4. Check that entryUSN values increment correctly without overflow -+ 5. Verify lastusn values are consistent -+ :expectedresults: -+ 1. Initial entryUSN values are recorded successfully -+ 2. Add operations fail with ALREADY_EXISTS error -+ 3. Modify operations succeed -+ 4. EntryUSN values increment properly without underflow/overflow -+ 5. LastUSN values are consistent and increasing -+ """ -+ -+ inst = topology_st.standalone -+ users = setup_usn_test -+ -+ # Enable detailed logging for debugging -+ config = Config(inst) -+ config.replace('nsslapd-accesslog-level', '260') # Internal op logging -+ config.replace('nsslapd-errorlog-level', '65536') -+ config.replace('nsslapd-plugin-logging', 'on') -+ -+ root_dse = RootDSE(inst) -+ -+ log.info("Starting entryUSN overflow reproduction test") -+ -+ # Record initial state -+ initial_usn_values = {} -+ for user in users: -+ initial_usn = user.get_attr_val_int('entryusn') -+ initial_usn_values[user.dn] = initial_usn -+ log.info(f"Initial entryUSN for {user.get_attr_val_utf8('cn')}: {initial_usn}") -+ -+ initial_lastusn = root_dse.get_attr_val_int("lastusn") -+ log.info(f"Initial lastUSN: {initial_lastusn}") -+ -+ # Perform test iterations -+ for iteration in range(1, ITERATIONS + 1): -+ log.info(f"\n--- Iteration {iteration} ---") -+ -+ # Step 1: Try to add existing entries multiple times -+ selected_user = random.choice(users) -+ cn_value = selected_user.get_attr_val_utf8('cn') -+ attempts = random.randint(1, ADD_EXISTING_ENTRY_MAX_ATTEMPTS) -+ -+ log.info(f"Attempting to add existing entry '{cn_value}' {attempts} times") -+ -+ # Get user attributes for recreation attempt -+ user_attrs = { -+ 'uid': selected_user.get_attr_val_utf8('uid'), -+ 'cn': selected_user.get_attr_val_utf8('cn'), -+ 'sn': selected_user.get_attr_val_utf8('sn'), -+ 'uidNumber': selected_user.get_attr_val_utf8('uidNumber'), -+ 'gidNumber': selected_user.get_attr_val_utf8('gidNumber'), -+ 'homeDirectory': selected_user.get_attr_val_utf8('homeDirectory'), -+ 'userPassword': 'password123' -+ } -+ -+ users_collection = UserAccounts(inst, DEFAULT_SUFFIX) -+ -+ # Try to add the existing user multiple times -+ for attempt in range(attempts): -+ try: -+ users_collection.create(properties=user_attrs) -+ log.error(f"ERROR: Add operation should have failed but succeeded on attempt {attempt + 1}") -+ assert False, "Add operation should have failed with ALREADY_EXISTS" -+ except ldap.ALREADY_EXISTS: -+ log.info(f"Attempt {attempt + 1}: Got expected ALREADY_EXISTS error") -+ except Exception as e: -+ log.error(f"Unexpected error on attempt {attempt + 1}: {e}") -+ raise -+ -+ # Step 2: Perform modify operation -+ target_user = random.choice(users) -+ cn_value = target_user.get_attr_val_utf8('cn') -+ old_usn = target_user.get_attr_val_int('entryusn') -+ -+ # Modify the user entry -+ new_description = f"Modified in iteration {iteration} - {time.time()}" -+ target_user.replace('description', new_description) -+ -+ # Get new USN value -+ new_usn = target_user.get_attr_val_int('entryusn') -+ -+ log.info(f"Modified entry '{cn_value}': old USN = {old_usn}, new USN = {new_usn}") -+ -+ # Step 3: Validate USN values -+ # Check for overflow/underflow conditions -+ assert new_usn > 0, f"EntryUSN should be positive, got {new_usn}" -+ assert new_usn < MAX_USN_64BIT, f"EntryUSN overflow detected: {new_usn} >= {MAX_USN_64BIT}" -+ -+ # Check that USN didn't wrap around (underflow detection) -+ usn_diff = new_usn - old_usn -+ assert usn_diff < 1000, f"USN increment too large, possible overflow: {usn_diff}" -+ -+ # Verify lastUSN is also reasonable -+ current_lastusn = root_dse.get_attr_val_int("lastusn") -+ assert current_lastusn >= new_usn, f"LastUSN ({current_lastusn}) should be >= entryUSN ({new_usn})" -+ assert current_lastusn < MAX_USN_64BIT, f"LastUSN overflow detected: {current_lastusn}" -+ -+ log.info(f"USN validation passed for iteration {iteration}") -+ -+ # Add a new entry occasionally to increase USN diversity -+ if iteration % 3 == 0: -+ new_user_props = { -+ 'uid': f'{TEST_USER_PREFIX}-new-{iteration}', -+ 'cn': f'{TEST_USER_PREFIX}-new-{iteration}', -+ 'sn': f'NewUser{iteration}', -+ 'uidNumber': str(2000 + iteration), -+ 'gidNumber': str(2000 + iteration), -+ 'homeDirectory': f'/home/{TEST_USER_PREFIX}-new-{iteration}', -+ 'userPassword': 'newpassword123' -+ } -+ try: -+ new_user = users_collection.create(properties=new_user_props) -+ new_user_usn = new_user.get_attr_val_int('entryusn') -+ log.info(f"Created new entry '{new_user.get_attr_val_utf8('cn')}' with USN: {new_user_usn}") -+ users.append(new_user) # Add to cleanup list -+ except Exception as e: -+ log.warning(f"Failed to create new user in iteration {iteration}: {e}") -+ -+ # Final validation: Check all USN values are reasonable -+ log.info("\nFinal USN validation") -+ final_lastusn = root_dse.get_attr_val_int("lastusn") -+ -+ for user in users: -+ try: -+ final_usn = user.get_attr_val_int('entryusn') -+ cn_value = user.get_attr_val_utf8('cn') -+ log.info(f"Final entryUSN for '{cn_value}': {final_usn}") -+ -+ # Ensure no overflow occurred -+ assert final_usn > 0, f"Final entryUSN should be positive for {cn_value}: {final_usn}" -+ assert final_usn < MAX_USN_64BIT, f"EntryUSN overflow for {cn_value}: {final_usn}" -+ -+ except ldap.NO_SUCH_OBJECT: -+ log.info(f"User {user.dn} was deleted during test") -+ -+ log.info(f"Final lastUSN: {final_lastusn}") -+ assert final_lastusn > initial_lastusn, "LastUSN should have increased during test" -+ assert final_lastusn < MAX_USN_64BIT, f"LastUSN overflow detected: {final_lastusn}" -+ -+ log.info("EntryUSN overflow test completed successfully") -+ -+ -+def test_entryusn_consistency_after_failed_adds(topology_st, setup_usn_test): -+ """Test that entryUSN remains consistent after failed add operations -+ -+ :id: e380ccad-527b-427e-a331-df5c41badbed -+ :setup: Standalone instance with USN plugin enabled and test users -+ :steps: -+ 1. Record entryUSN values before failed add attempts -+ 2. Attempt to add existing entries (should fail) -+ 3. Verify entryUSN values haven't changed due to failed operations -+ 4. Perform successful modify operations -+ 5. Verify entryUSN increments correctly -+ :expectedresults: -+ 1. Initial entryUSN values recorded -+ 2. Add operations fail as expected -+ 3. EntryUSN values unchanged after failed adds -+ 4. Modify operations succeed -+ 5. EntryUSN values increment correctly without overflow -+ """ -+ -+ inst = topology_st.standalone -+ users = setup_usn_test -+ -+ log.info("Testing entryUSN consistency after failed adds") -+ -+ # Record USN values before any operations -+ pre_operation_usns = {} -+ for user in users: -+ usn = user.get_attr_val_int('entryusn') -+ pre_operation_usns[user.dn] = usn -+ log.info(f"Pre-operation entryUSN for {user.get_attr_val_utf8('cn')}: {usn}") -+ -+ # Attempt to add existing entries - these should fail -+ users_collection = UserAccounts(inst, DEFAULT_SUFFIX) -+ -+ for user in users: -+ cn_value = user.get_attr_val_utf8('cn') -+ log.info(f"Attempting to add existing user: {cn_value}") -+ -+ user_attrs = { -+ 'uid': user.get_attr_val_utf8('uid'), -+ 'cn': cn_value, -+ 'sn': user.get_attr_val_utf8('sn'), -+ 'uidNumber': user.get_attr_val_utf8('uidNumber'), -+ 'gidNumber': user.get_attr_val_utf8('gidNumber'), -+ 'homeDirectory': user.get_attr_val_utf8('homeDirectory'), -+ 'userPassword': 'password123' -+ } -+ -+ try: -+ users_collection.create(properties=user_attrs) -+ assert False, f"Add operation should have failed for existing user {cn_value}" -+ except ldap.ALREADY_EXISTS: -+ log.info(f"Got expected ALREADY_EXISTS for {cn_value}") -+ -+ # Verify USN values haven't changed after failed adds -+ log.info("Verifying entryUSN values after failed add operations...") -+ for user in users: -+ current_usn = user.get_attr_val_int('entryusn') -+ expected_usn = pre_operation_usns[user.dn] -+ cn_value = user.get_attr_val_utf8('cn') -+ -+ assert current_usn == expected_usn, \ -+ f"EntryUSN changed after failed add for {cn_value}: was {expected_usn}, now {current_usn}" -+ log.info(f"EntryUSN unchanged for {cn_value}: {current_usn}") -+ -+ # Now perform successful modify operations -+ log.info("Performing successful modify operations...") -+ for i, user in enumerate(users): -+ cn_value = user.get_attr_val_utf8('cn') -+ old_usn = user.get_attr_val_int('entryusn') -+ -+ # Modify the user -+ user.replace('description', f'Consistency test modification {i + 1}') -+ -+ new_usn = user.get_attr_val_int('entryusn') -+ log.info(f"Modified {cn_value}: USN {old_usn} -> {new_usn}") -+ -+ # Verify proper increment -+ assert (new_usn - old_usn) == 1, f"EntryUSN should increment by 1 for {cn_value}: {old_usn} -> {new_usn}" -+ assert new_usn < MAX_USN_64BIT, f"EntryUSN overflow for {cn_value}: {new_usn}" -+ -+ log.info("EntryUSN consistency test completed successfully") -+ -+ -+if __name__ == '__main__': -+ # Run isolated -+ # -s for DEBUG mode -+ CURRENT_FILE = os.path.realpath(__file__) -+ pytest.main("-s %s" % CURRENT_FILE) -\ No newline at end of file --- -2.49.0 - diff --git a/0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch b/0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch deleted file mode 100644 index 9680aa3..0000000 --- a/0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch +++ /dev/null @@ -1,172 +0,0 @@ -From 37a56f75afac2805e1ba958eebd496e77b7079e7 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Mon, 28 Jul 2025 15:35:50 -0700 -Subject: [PATCH] Issue 6594 - Add test for numSubordinates replication - consistency with tombstones (#6862) - -Description: Add a comprehensive test to verify that numSubordinates and -tombstoneNumSubordinates attributes are correctly replicated between -instances when tombstone entries are present. - -Fixes: https://github.com/389ds/389-ds-base/issues/6594 - -Reviewed by: @progier389 (Thanks!) ---- - .../numsubordinates_replication_test.py | 144 ++++++++++++++++++ - 1 file changed, 144 insertions(+) - create mode 100644 dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py - -diff --git a/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py b/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py -new file mode 100644 -index 000000000..9ba10657d ---- /dev/null -+++ b/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py -@@ -0,0 +1,144 @@ -+# --- BEGIN COPYRIGHT BLOCK --- -+# Copyright (C) 2025 Red Hat, Inc. -+# All rights reserved. -+# -+# License: GPL (version 3 or any later version). -+# See LICENSE for details. -+# --- END COPYRIGHT BLOCK --- -+ -+import os -+import logging -+import pytest -+from lib389._constants import DEFAULT_SUFFIX -+from lib389.replica import ReplicationManager -+from lib389.idm.organizationalunit import OrganizationalUnits -+from lib389.idm.user import UserAccounts -+from lib389.topologies import topology_i2 as topo_i2 -+ -+ -+pytestmark = pytest.mark.tier1 -+ -+DEBUGGING = os.getenv("DEBUGGING", default=False) -+if DEBUGGING: -+ logging.getLogger(__name__).setLevel(logging.DEBUG) -+else: -+ logging.getLogger(__name__).setLevel(logging.INFO) -+log = logging.getLogger(__name__) -+ -+ -+def test_numsubordinates_tombstone_replication_mismatch(topo_i2): -+ """Test that numSubordinates values match between replicas after tombstone creation -+ -+ :id: c43ecc7a-d706-42e8-9179-1ff7d0e7163a -+ :setup: Two standalone instances -+ :steps: -+ 1. Create a container (organizational unit) on the first instance -+ 2. Create a user object in that container -+ 3. Delete the user object (this creates a tombstone) -+ 4. Set up replication between the two instances -+ 5. Wait for replication to complete -+ 6. Check numSubordinates on both instances -+ 7. Check tombstoneNumSubordinates on both instances -+ 8. Verify that numSubordinates values match on both instances -+ :expectedresults: -+ 1. Container should be created successfully -+ 2. User object should be created successfully -+ 3. User object should be deleted successfully -+ 4. Replication should be set up successfully -+ 5. Replication should complete successfully -+ 6. numSubordinates should be accessible on both instances -+ 7. tombstoneNumSubordinates should be accessible on both instances -+ 8. numSubordinates values should match on both instances -+ """ -+ -+ instance1 = topo_i2.ins["standalone1"] -+ instance2 = topo_i2.ins["standalone2"] -+ -+ log.info("Create a container (organizational unit) on the first instance") -+ ous1 = OrganizationalUnits(instance1, DEFAULT_SUFFIX) -+ container = ous1.create(properties={ -+ 'ou': 'test_container', -+ 'description': 'Test container for numSubordinates replication test' -+ }) -+ container_rdn = container.rdn -+ log.info(f"Created container: {container_rdn}") -+ -+ log.info("Create a user object in that container") -+ users1 = UserAccounts(instance1, DEFAULT_SUFFIX, rdn=f"ou={container_rdn}") -+ test_user = users1.create_test_user(uid=1001) -+ log.info(f"Created user: {test_user.dn}") -+ -+ log.info("Checking initial numSubordinates on container") -+ container_obj1 = OrganizationalUnits(instance1, DEFAULT_SUFFIX).get(container_rdn) -+ initial_numsubordinates = container_obj1.get_attr_val_int('numSubordinates') -+ log.info(f"Initial numSubordinates: {initial_numsubordinates}") -+ assert initial_numsubordinates == 1 -+ -+ log.info("Delete the user object (this creates a tombstone)") -+ test_user.delete() -+ -+ log.info("Checking numSubordinates after deletion") -+ after_delete_numsubordinates = container_obj1.get_attr_val_int('numSubordinates') -+ log.info(f"numSubordinates after deletion: {after_delete_numsubordinates}") -+ -+ log.info("Checking tombstoneNumSubordinates after deletion") -+ try: -+ tombstone_numsubordinates = container_obj1.get_attr_val_int('tombstoneNumSubordinates') -+ log.info(f"tombstoneNumSubordinates: {tombstone_numsubordinates}") -+ except Exception as e: -+ log.info(f"tombstoneNumSubordinates not found or error: {e}") -+ tombstone_numsubordinates = 0 -+ -+ log.info("Set up replication between the two instances") -+ repl = ReplicationManager(DEFAULT_SUFFIX) -+ repl.create_first_supplier(instance1) -+ repl.join_supplier(instance1, instance2) -+ -+ log.info("Wait for replication to complete") -+ repl.wait_for_replication(instance1, instance2) -+ -+ log.info("Check numSubordinates on both instances") -+ container_obj1 = OrganizationalUnits(instance1, DEFAULT_SUFFIX).get(container_rdn) -+ numsubordinates_instance1 = container_obj1.get_attr_val_int('numSubordinates') -+ log.info(f"numSubordinates on instance1: {numsubordinates_instance1}") -+ -+ container_obj2 = OrganizationalUnits(instance2, DEFAULT_SUFFIX).get(container_rdn) -+ numsubordinates_instance2 = container_obj2.get_attr_val_int('numSubordinates') -+ log.info(f"numSubordinates on instance2: {numsubordinates_instance2}") -+ -+ log.info("Check tombstoneNumSubordinates on both instances") -+ try: -+ tombstone_numsubordinates_instance1 = container_obj1.get_attr_val_int('tombstoneNumSubordinates') -+ log.info(f"tombstoneNumSubordinates on instance1: {tombstone_numsubordinates_instance1}") -+ except Exception as e: -+ log.info(f"tombstoneNumSubordinates not found on instance1: {e}") -+ tombstone_numsubordinates_instance1 = 0 -+ -+ try: -+ tombstone_numsubordinates_instance2 = container_obj2.get_attr_val_int('tombstoneNumSubordinates') -+ log.info(f"tombstoneNumSubordinates on instance2: {tombstone_numsubordinates_instance2}") -+ except Exception as e: -+ log.info(f"tombstoneNumSubordinates not found on instance2: {e}") -+ tombstone_numsubordinates_instance2 = 0 -+ -+ log.info("Verify that numSubordinates values match on both instances") -+ log.info(f"Comparison: instance1 numSubordinates={numsubordinates_instance1}, " -+ f"instance2 numSubordinates={numsubordinates_instance2}") -+ log.info(f"Comparison: instance1 tombstoneNumSubordinates={tombstone_numsubordinates_instance1}, " -+ f"instance2 tombstoneNumSubordinates={tombstone_numsubordinates_instance2}") -+ -+ assert numsubordinates_instance1 == numsubordinates_instance2, ( -+ f"numSubordinates mismatch: instance1 has {numsubordinates_instance1}, " -+ f"instance2 has {numsubordinates_instance2}. " -+ ) -+ assert tombstone_numsubordinates_instance1 == tombstone_numsubordinates_instance2, ( -+ f"tombstoneNumSubordinates mismatch: instance1 has {tombstone_numsubordinates_instance1}, " -+ f"instance2 has {tombstone_numsubordinates_instance2}. " -+ ) -+ -+ -+if __name__ == '__main__': -+ # Run isolated -+ # -s for DEBUG mode -+ CURRENT_FILE = os.path.realpath(__file__) -+ pytest.main("-s %s" % CURRENT_FILE) -\ No newline at end of file --- -2.49.0 - diff --git a/0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch b/0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch deleted file mode 100644 index f7f9a2b..0000000 --- a/0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch +++ /dev/null @@ -1,814 +0,0 @@ -From e05653cbff500c47b89e43e4a1c85b7cb30321ff Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Mon, 28 Jul 2025 15:41:29 -0700 -Subject: [PATCH] Issue 6884 - Mask password hashes in audit logs (#6885) - -Description: Fix the audit log functionality to mask password hash values for -userPassword, nsslapd-rootpw, nsmultiplexorcredentials, nsds5ReplicaCredentials, -and nsds5ReplicaBootstrapCredentials attributes in ADD and MODIFY operations. -Update auditlog.c to detect password attributes and replace their values with -asterisks (**********************) in both LDIF and JSON audit log formats. -Add a comprehensive test suite audit_password_masking_test.py to verify -password masking works correctly across all log formats and operation types. - -Fixes: https://github.com/389ds/389-ds-base/issues/6884 - -Reviewed by: @mreynolds389, @vashirov (Thanks!!) ---- - .../logging/audit_password_masking_test.py | 501 ++++++++++++++++++ - ldap/servers/slapd/auditlog.c | 170 +++++- - ldap/servers/slapd/slapi-private.h | 1 + - src/lib389/lib389/chaining.py | 3 +- - 4 files changed, 652 insertions(+), 23 deletions(-) - create mode 100644 dirsrvtests/tests/suites/logging/audit_password_masking_test.py - -diff --git a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py -new file mode 100644 -index 000000000..3b6a54849 ---- /dev/null -+++ b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py -@@ -0,0 +1,501 @@ -+# --- BEGIN COPYRIGHT BLOCK --- -+# Copyright (C) 2025 Red Hat, Inc. -+# All rights reserved. -+# -+# License: GPL (version 3 or any later version). -+# See LICENSE for details. -+# --- END COPYRIGHT BLOCK --- -+# -+import logging -+import pytest -+import os -+import re -+import time -+import ldap -+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PW_DM -+from lib389.topologies import topology_m2 as topo -+from lib389.idm.user import UserAccounts -+from lib389.dirsrv_log import DirsrvAuditJSONLog -+from lib389.plugins import ChainingBackendPlugin -+from lib389.chaining import ChainingLinks -+from lib389.agreement import Agreements -+from lib389.replica import ReplicationManager, Replicas -+from lib389.idm.directorymanager import DirectoryManager -+ -+log = logging.getLogger(__name__) -+ -+MASKED_PASSWORD = "**********************" -+TEST_PASSWORD = "MySecret123" -+TEST_PASSWORD_2 = "NewPassword789" -+TEST_PASSWORD_3 = "NewPassword101" -+ -+ -+def setup_audit_logging(inst, log_format='default', display_attrs=None): -+ """Configure audit logging settings""" -+ inst.config.replace('nsslapd-auditlog-logbuffering', 'off') -+ inst.config.replace('nsslapd-auditlog-logging-enabled', 'on') -+ inst.config.replace('nsslapd-auditlog-log-format', log_format) -+ -+ if display_attrs is not None: -+ inst.config.replace('nsslapd-auditlog-display-attrs', display_attrs) -+ -+ inst.deleteAuditLogs() -+ -+ -+def check_password_masked(inst, log_format, expected_password, actual_password): -+ """Helper function to check password masking in audit logs""" -+ -+ time.sleep(1) # Allow log to flush -+ -+ # List of all password/credential attributes that should be masked -+ password_attributes = [ -+ 'userPassword', -+ 'nsslapd-rootpw', -+ 'nsmultiplexorcredentials', -+ 'nsDS5ReplicaCredentials', -+ 'nsDS5ReplicaBootstrapCredentials' -+ ] -+ -+ # Get password schemes to check for hash leakage -+ user_password_scheme = inst.config.get_attr_val_utf8('passwordStorageScheme') -+ root_password_scheme = inst.config.get_attr_val_utf8('nsslapd-rootpwstoragescheme') -+ -+ if log_format == 'json': -+ # Check JSON format logs -+ audit_log = DirsrvAuditJSONLog(inst) -+ log_lines = audit_log.readlines() -+ -+ found_masked = False -+ found_actual = False -+ found_hashed = False -+ -+ for line in log_lines: -+ # Check if any password attribute is present in the line -+ for attr in password_attributes: -+ if attr in line: -+ if expected_password in line: -+ found_masked = True -+ if actual_password in line: -+ found_actual = True -+ # Check for password scheme indicators (hashed passwords) -+ if user_password_scheme and f'{{{user_password_scheme}}}' in line: -+ found_hashed = True -+ if root_password_scheme and f'{{{root_password_scheme}}}' in line: -+ found_hashed = True -+ break # Found a password attribute, no need to check others for this line -+ -+ else: -+ # Check LDIF format logs -+ found_masked = False -+ found_actual = False -+ found_hashed = False -+ -+ # Check each password attribute for masked password -+ for attr in password_attributes: -+ if inst.ds_audit_log.match(f"{attr}: {re.escape(expected_password)}"): -+ found_masked = True -+ if inst.ds_audit_log.match(f"{attr}: {actual_password}"): -+ found_actual = True -+ -+ # Check for hashed passwords in LDIF format -+ if user_password_scheme: -+ if inst.ds_audit_log.match(f"userPassword: {{{user_password_scheme}}}"): -+ found_hashed = True -+ if root_password_scheme: -+ if inst.ds_audit_log.match(f"nsslapd-rootpw: {{{root_password_scheme}}}"): -+ found_hashed = True -+ -+ # Delete audit logs to avoid interference with other tests -+ # We need to reset the root password to default as deleteAuditLogs() -+ # opens a new connection with the default password -+ dm = DirectoryManager(inst) -+ dm.change_password(PW_DM) -+ inst.deleteAuditLogs() -+ -+ return found_masked, found_actual, found_hashed -+ -+ -+@pytest.mark.parametrize("log_format,display_attrs", [ -+ ("default", None), -+ ("default", "*"), -+ ("default", "userPassword"), -+ ("json", None), -+ ("json", "*"), -+ ("json", "userPassword") -+]) -+def test_password_masking_add_operation(topo, log_format, display_attrs): -+ """Test password masking in ADD operations -+ -+ :id: 4358bd75-bcc7-401c-b492-d3209b10412d -+ :parametrized: yes -+ :setup: Standalone Instance -+ :steps: -+ 1. Configure audit logging format -+ 2. Add user with password -+ 3. Check that password is masked in audit log -+ 4. Verify actual password does not appear in log -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Password should be masked with asterisks -+ 4. Actual password should not be found in log -+ """ -+ inst = topo.ms['supplier1'] -+ setup_audit_logging(inst, log_format, display_attrs) -+ -+ users = UserAccounts(inst, DEFAULT_SUFFIX) -+ user = None -+ -+ try: -+ user = users.create(properties={ -+ 'uid': 'test_add_pwd_mask', -+ 'cn': 'Test Add User', -+ 'sn': 'User', -+ 'uidNumber': '1000', -+ 'gidNumber': '1000', -+ 'homeDirectory': '/home/test_add', -+ 'userPassword': TEST_PASSWORD -+ }) -+ -+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD) -+ -+ assert found_masked, f"Masked password not found in {log_format} ADD operation" -+ assert not found_actual, f"Actual password found in {log_format} ADD log (should be masked)" -+ assert not found_hashed, f"Hashed password found in {log_format} ADD log (should be masked)" -+ -+ finally: -+ if user is not None: -+ try: -+ user.delete() -+ except: -+ pass -+ -+ -+@pytest.mark.parametrize("log_format,display_attrs", [ -+ ("default", None), -+ ("default", "*"), -+ ("default", "userPassword"), -+ ("json", None), -+ ("json", "*"), -+ ("json", "userPassword") -+]) -+def test_password_masking_modify_operation(topo, log_format, display_attrs): -+ """Test password masking in MODIFY operations -+ -+ :id: e6963aa9-7609-419c-aae2-1d517aa434bd -+ :parametrized: yes -+ :setup: Standalone Instance -+ :steps: -+ 1. Configure audit logging format -+ 2. Add user without password -+ 3. Add password via MODIFY operation -+ 4. Check that password is masked in audit log -+ 5. Modify password to new value -+ 6. Check that new password is also masked -+ 7. Verify actual passwords do not appear in log -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Password should be masked with asterisks -+ 5. Success -+ 6. New password should be masked with asterisks -+ 7. No actual password values should be found in log -+ """ -+ inst = topo.ms['supplier1'] -+ setup_audit_logging(inst, log_format, display_attrs) -+ -+ users = UserAccounts(inst, DEFAULT_SUFFIX) -+ user = None -+ -+ try: -+ user = users.create(properties={ -+ 'uid': 'test_modify_pwd_mask', -+ 'cn': 'Test Modify User', -+ 'sn': 'User', -+ 'uidNumber': '2000', -+ 'gidNumber': '2000', -+ 'homeDirectory': '/home/test_modify' -+ }) -+ -+ user.replace('userPassword', TEST_PASSWORD) -+ -+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD) -+ assert found_masked, f"Masked password not found in {log_format} MODIFY operation (first password)" -+ assert not found_actual, f"Actual password found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed, f"Hashed password found in {log_format} MODIFY log (should be masked)" -+ -+ user.replace('userPassword', TEST_PASSWORD_2) -+ -+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2) -+ assert found_masked_2, f"Masked password not found in {log_format} MODIFY operation (second password)" -+ assert not found_actual_2, f"Second actual password found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed_2, f"Second hashed password found in {log_format} MODIFY log (should be masked)" -+ -+ finally: -+ if user is not None: -+ try: -+ user.delete() -+ except: -+ pass -+ -+ -+@pytest.mark.parametrize("log_format,display_attrs", [ -+ ("default", None), -+ ("default", "*"), -+ ("default", "nsslapd-rootpw"), -+ ("json", None), -+ ("json", "*"), -+ ("json", "nsslapd-rootpw") -+]) -+def test_password_masking_rootpw_modify_operation(topo, log_format, display_attrs): -+ """Test password masking for nsslapd-rootpw MODIFY operations -+ -+ :id: ec8c9fd4-56ba-4663-ab65-58efb3b445e4 -+ :parametrized: yes -+ :setup: Standalone Instance -+ :steps: -+ 1. Configure audit logging format -+ 2. Modify nsslapd-rootpw in configuration -+ 3. Check that root password is masked in audit log -+ 4. Modify root password to new value -+ 5. Check that new root password is also masked -+ 6. Verify actual root passwords do not appear in log -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Root password should be masked with asterisks -+ 4. Success -+ 5. New root password should be masked with asterisks -+ 6. No actual root password values should be found in log -+ """ -+ inst = topo.ms['supplier1'] -+ setup_audit_logging(inst, log_format, display_attrs) -+ dm = DirectoryManager(inst) -+ -+ try: -+ dm.change_password(TEST_PASSWORD) -+ dm.rebind(TEST_PASSWORD) -+ -+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD) -+ assert found_masked, f"Masked root password not found in {log_format} MODIFY operation (first root password)" -+ assert not found_actual, f"Actual root password found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed, f"Hashed root password found in {log_format} MODIFY log (should be masked)" -+ -+ dm.change_password(TEST_PASSWORD_2) -+ dm.rebind(TEST_PASSWORD_2) -+ -+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2) -+ assert found_masked_2, f"Masked root password not found in {log_format} MODIFY operation (second root password)" -+ assert not found_actual_2, f"Second actual root password found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed_2, f"Second hashed root password found in {log_format} MODIFY log (should be masked)" -+ -+ finally: -+ dm.change_password(PW_DM) -+ dm.rebind(PW_DM) -+ -+ -+@pytest.mark.parametrize("log_format,display_attrs", [ -+ ("default", None), -+ ("default", "*"), -+ ("default", "nsmultiplexorcredentials"), -+ ("json", None), -+ ("json", "*"), -+ ("json", "nsmultiplexorcredentials") -+]) -+def test_password_masking_multiplexor_credentials(topo, log_format, display_attrs): -+ """Test password masking for nsmultiplexorcredentials in chaining/multiplexor configurations -+ -+ :id: 161a9498-b248-4926-90be-a696a36ed36e -+ :parametrized: yes -+ :setup: Standalone Instance -+ :steps: -+ 1. Configure audit logging format -+ 2. Create a chaining backend configuration entry with nsmultiplexorcredentials -+ 3. Check that multiplexor credentials are masked in audit log -+ 4. Modify the credentials -+ 5. Check that updated credentials are also masked -+ 6. Verify actual credentials do not appear in log -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Multiplexor credentials should be masked with asterisks -+ 4. Success -+ 5. Updated credentials should be masked with asterisks -+ 6. No actual credential values should be found in log -+ """ -+ inst = topo.ms['supplier1'] -+ setup_audit_logging(inst, log_format, display_attrs) -+ -+ # Enable chaining plugin and create chaining link -+ chain_plugin = ChainingBackendPlugin(inst) -+ chain_plugin.enable() -+ -+ chains = ChainingLinks(inst) -+ chain = None -+ -+ try: -+ # Create chaining link with multiplexor credentials -+ chain = chains.create(properties={ -+ 'cn': 'testchain', -+ 'nsfarmserverurl': 'ldap://localhost:389/', -+ 'nsslapd-suffix': 'dc=example,dc=com', -+ 'nsmultiplexorbinddn': 'cn=manager', -+ 'nsmultiplexorcredentials': TEST_PASSWORD, -+ 'nsCheckLocalACI': 'on', -+ 'nsConnectionLife': '30', -+ }) -+ -+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD) -+ assert found_masked, f"Masked multiplexor credentials not found in {log_format} ADD operation" -+ assert not found_actual, f"Actual multiplexor credentials found in {log_format} ADD log (should be masked)" -+ assert not found_hashed, f"Hashed multiplexor credentials found in {log_format} ADD log (should be masked)" -+ -+ # Modify the credentials -+ chain.replace('nsmultiplexorcredentials', TEST_PASSWORD_2) -+ -+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2) -+ assert found_masked_2, f"Masked multiplexor credentials not found in {log_format} MODIFY operation" -+ assert not found_actual_2, f"Actual multiplexor credentials found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed_2, f"Hashed multiplexor credentials found in {log_format} MODIFY log (should be masked)" -+ -+ finally: -+ chain_plugin.disable() -+ if chain is not None: -+ inst.delete_branch_s(chain.dn, ldap.SCOPE_ONELEVEL) -+ chain.delete() -+ -+ -+@pytest.mark.parametrize("log_format,display_attrs", [ -+ ("default", None), -+ ("default", "*"), -+ ("default", "nsDS5ReplicaCredentials"), -+ ("json", None), -+ ("json", "*"), -+ ("json", "nsDS5ReplicaCredentials") -+]) -+def test_password_masking_replica_credentials(topo, log_format, display_attrs): -+ """Test password masking for nsDS5ReplicaCredentials in replication agreements -+ -+ :id: 7bf9e612-1b7c-49af-9fc0-de4c7df84b2a -+ :parametrized: yes -+ :setup: Standalone Instance -+ :steps: -+ 1. Configure audit logging format -+ 2. Create a replication agreement entry with nsDS5ReplicaCredentials -+ 3. Check that replica credentials are masked in audit log -+ 4. Modify the credentials -+ 5. Check that updated credentials are also masked -+ 6. Verify actual credentials do not appear in log -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Replica credentials should be masked with asterisks -+ 4. Success -+ 5. Updated credentials should be masked with asterisks -+ 6. No actual credential values should be found in log -+ """ -+ inst = topo.ms['supplier2'] -+ setup_audit_logging(inst, log_format, display_attrs) -+ agmt = None -+ -+ try: -+ replicas = Replicas(inst) -+ replica = replicas.get(DEFAULT_SUFFIX) -+ agmts = replica.get_agreements() -+ agmt = agmts.create(properties={ -+ 'cn': 'testagmt', -+ 'nsDS5ReplicaHost': 'localhost', -+ 'nsDS5ReplicaPort': '389', -+ 'nsDS5ReplicaBindDN': 'cn=replication manager,cn=config', -+ 'nsDS5ReplicaCredentials': TEST_PASSWORD, -+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX -+ }) -+ -+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD) -+ assert found_masked, f"Masked replica credentials not found in {log_format} ADD operation" -+ assert not found_actual, f"Actual replica credentials found in {log_format} ADD log (should be masked)" -+ assert not found_hashed, f"Hashed replica credentials found in {log_format} ADD log (should be masked)" -+ -+ # Modify the credentials -+ agmt.replace('nsDS5ReplicaCredentials', TEST_PASSWORD_2) -+ -+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2) -+ assert found_masked_2, f"Masked replica credentials not found in {log_format} MODIFY operation" -+ assert not found_actual_2, f"Actual replica credentials found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed_2, f"Hashed replica credentials found in {log_format} MODIFY log (should be masked)" -+ -+ finally: -+ if agmt is not None: -+ agmt.delete() -+ -+ -+@pytest.mark.parametrize("log_format,display_attrs", [ -+ ("default", None), -+ ("default", "*"), -+ ("default", "nsDS5ReplicaBootstrapCredentials"), -+ ("json", None), -+ ("json", "*"), -+ ("json", "nsDS5ReplicaBootstrapCredentials") -+]) -+def test_password_masking_bootstrap_credentials(topo, log_format, display_attrs): -+ """Test password masking for nsDS5ReplicaCredentials and nsDS5ReplicaBootstrapCredentials in replication agreements -+ -+ :id: 248bd418-ffa4-4733-963d-2314c60b7c5b -+ :parametrized: yes -+ :setup: Standalone Instance -+ :steps: -+ 1. Configure audit logging format -+ 2. Create a replication agreement entry with both nsDS5ReplicaCredentials and nsDS5ReplicaBootstrapCredentials -+ 3. Check that both credentials are masked in audit log -+ 4. Modify both credentials -+ 5. Check that both updated credentials are also masked -+ 6. Verify actual credentials do not appear in log -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Both credentials should be masked with asterisks -+ 4. Success -+ 5. Both updated credentials should be masked with asterisks -+ 6. No actual credential values should be found in log -+ """ -+ inst = topo.ms['supplier2'] -+ setup_audit_logging(inst, log_format, display_attrs) -+ agmt = None -+ -+ try: -+ replicas = Replicas(inst) -+ replica = replicas.get(DEFAULT_SUFFIX) -+ agmts = replica.get_agreements() -+ agmt = agmts.create(properties={ -+ 'cn': 'testbootstrapagmt', -+ 'nsDS5ReplicaHost': 'localhost', -+ 'nsDS5ReplicaPort': '389', -+ 'nsDS5ReplicaBindDN': 'cn=replication manager,cn=config', -+ 'nsDS5ReplicaCredentials': TEST_PASSWORD, -+ 'nsDS5replicabootstrapbinddn': 'cn=bootstrap manager,cn=config', -+ 'nsDS5ReplicaBootstrapCredentials': TEST_PASSWORD_2, -+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX -+ }) -+ -+ found_masked_bootstrap, found_actual_bootstrap, found_hashed_bootstrap = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2) -+ assert found_masked_bootstrap, f"Masked bootstrap credentials not found in {log_format} ADD operation" -+ assert not found_actual_bootstrap, f"Actual bootstrap credentials found in {log_format} ADD log (should be masked)" -+ assert not found_hashed_bootstrap, f"Hashed bootstrap credentials found in {log_format} ADD log (should be masked)" -+ -+ agmt.replace('nsDS5ReplicaBootstrapCredentials', TEST_PASSWORD_3) -+ -+ found_masked_bootstrap_2, found_actual_bootstrap_2, found_hashed_bootstrap_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_3) -+ assert found_masked_bootstrap_2, f"Masked bootstrap credentials not found in {log_format} MODIFY operation" -+ assert not found_actual_bootstrap_2, f"Actual bootstrap credentials found in {log_format} MODIFY log (should be masked)" -+ assert not found_hashed_bootstrap_2, f"Hashed bootstrap credentials found in {log_format} MODIFY log (should be masked)" -+ -+ finally: -+ if agmt is not None: -+ agmt.delete() -+ -+ -+ -+if __name__ == '__main__': -+ CURRENT_FILE = os.path.realpath(__file__) -+ pytest.main(["-s", CURRENT_FILE]) -\ No newline at end of file -diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c -index 3945b0533..3a34959f6 100644 ---- a/ldap/servers/slapd/auditlog.c -+++ b/ldap/servers/slapd/auditlog.c -@@ -39,6 +39,89 @@ static void write_audit_file(Slapi_PBlock *pb, Slapi_Entry *entry, int logtype, - - static const char *modrdn_changes[4]; - -+/* Helper function to check if an attribute is a password that needs masking */ -+static int -+is_password_attribute(const char *attr_name) -+{ -+ return (strcasecmp(attr_name, SLAPI_USERPWD_ATTR) == 0 || -+ strcasecmp(attr_name, CONFIG_ROOTPW_ATTRIBUTE) == 0 || -+ strcasecmp(attr_name, SLAPI_MB_CREDENTIALS) == 0 || -+ strcasecmp(attr_name, SLAPI_REP_CREDENTIALS) == 0 || -+ strcasecmp(attr_name, SLAPI_REP_BOOTSTRAP_CREDENTIALS) == 0); -+} -+ -+/* Helper function to create a masked string representation of an entry */ -+static char * -+create_masked_entry_string(Slapi_Entry *original_entry, int *len) -+{ -+ Slapi_Attr *attr = NULL; -+ char *entry_str = NULL; -+ char *current_pos = NULL; -+ char *line_start = NULL; -+ char *next_line = NULL; -+ char *colon_pos = NULL; -+ int has_password_attrs = 0; -+ -+ if (original_entry == NULL) { -+ return NULL; -+ } -+ -+ /* Single pass through attributes to check for password attributes */ -+ for (slapi_entry_first_attr(original_entry, &attr); attr != NULL; -+ slapi_entry_next_attr(original_entry, attr, &attr)) { -+ -+ char *attr_name = NULL; -+ slapi_attr_get_type(attr, &attr_name); -+ -+ if (is_password_attribute(attr_name)) { -+ has_password_attrs = 1; -+ break; -+ } -+ } -+ -+ /* If no password attributes, return original string - no masking needed */ -+ entry_str = slapi_entry2str(original_entry, len); -+ if (!has_password_attrs) { -+ return entry_str; -+ } -+ -+ /* Process the string in-place, replacing password values */ -+ current_pos = entry_str; -+ while ((line_start = current_pos) != NULL && *line_start != '\0') { -+ /* Find the end of current line */ -+ next_line = strchr(line_start, '\n'); -+ if (next_line != NULL) { -+ *next_line = '\0'; /* Temporarily terminate line */ -+ current_pos = next_line + 1; -+ } else { -+ current_pos = NULL; /* Last line */ -+ } -+ -+ /* Find the colon that separates attribute name from value */ -+ colon_pos = strchr(line_start, ':'); -+ if (colon_pos != NULL) { -+ char saved_colon = *colon_pos; -+ *colon_pos = '\0'; /* Temporarily null-terminate attribute name */ -+ -+ /* Check if this is a password attribute that needs masking */ -+ if (is_password_attribute(line_start)) { -+ strcpy(colon_pos + 1, " **********************"); -+ } -+ -+ *colon_pos = saved_colon; /* Restore colon */ -+ } -+ -+ /* Restore newline if it was there */ -+ if (next_line != NULL) { -+ *next_line = '\n'; -+ } -+ } -+ -+ /* Update length since we may have shortened the string */ -+ *len = strlen(entry_str); -+ return entry_str; /* Return the modified original string */ -+} -+ - void - write_audit_log_entry(Slapi_PBlock *pb) - { -@@ -279,10 +362,31 @@ add_entry_attrs_ext(Slapi_Entry *entry, lenstr *l, PRBool use_json, json_object - { - slapi_entry_attr_find(entry, req_attr, &entry_attr); - if (entry_attr) { -- if (use_json) { -- log_entry_attr_json(entry_attr, req_attr, id_list); -+ if (strcmp(req_attr, PSEUDO_ATTR_UNHASHEDUSERPASSWORD) == 0) { -+ /* Do not write the unhashed clear-text password */ -+ continue; -+ } -+ -+ /* Check if this is a password attribute that needs masking */ -+ if (is_password_attribute(req_attr)) { -+ /* userpassword/rootdn password - mask the value */ -+ if (use_json) { -+ json_object *secret_obj = json_object_new_object(); -+ json_object_object_add(secret_obj, req_attr, -+ json_object_new_string("**********************")); -+ json_object_array_add(id_list, secret_obj); -+ } else { -+ addlenstr(l, "#"); -+ addlenstr(l, req_attr); -+ addlenstr(l, ": **********************\n"); -+ } - } else { -- log_entry_attr(entry_attr, req_attr, l); -+ /* Regular attribute - log normally */ -+ if (use_json) { -+ log_entry_attr_json(entry_attr, req_attr, id_list); -+ } else { -+ log_entry_attr(entry_attr, req_attr, l); -+ } - } - } - } -@@ -297,9 +401,7 @@ add_entry_attrs_ext(Slapi_Entry *entry, lenstr *l, PRBool use_json, json_object - continue; - } - -- if (strcasecmp(attr, SLAPI_USERPWD_ATTR) == 0 || -- strcasecmp(attr, CONFIG_ROOTPW_ATTRIBUTE) == 0) -- { -+ if (is_password_attribute(attr)) { - /* userpassword/rootdn password - mask the value */ - if (use_json) { - json_object *secret_obj = json_object_new_object(); -@@ -309,7 +411,7 @@ add_entry_attrs_ext(Slapi_Entry *entry, lenstr *l, PRBool use_json, json_object - } else { - addlenstr(l, "#"); - addlenstr(l, attr); -- addlenstr(l, ": ****************************\n"); -+ addlenstr(l, ": **********************\n"); - } - continue; - } -@@ -478,6 +580,9 @@ write_audit_file_json(Slapi_PBlock *pb, Slapi_Entry *entry, int logtype, - } - } - -+ /* Check if this is a password attribute that needs masking */ -+ int is_password_attr = is_password_attribute(mods[j]->mod_type); -+ - mod = json_object_new_object(); - switch (operationtype) { - case LDAP_MOD_ADD: -@@ -502,7 +607,12 @@ write_audit_file_json(Slapi_PBlock *pb, Slapi_Entry *entry, int logtype, - json_object *val_list = NULL; - val_list = json_object_new_array(); - for (size_t i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) { -- json_object_array_add(val_list, json_object_new_string(mods[j]->mod_bvalues[i]->bv_val)); -+ if (is_password_attr) { -+ /* Mask password values */ -+ json_object_array_add(val_list, json_object_new_string("**********************")); -+ } else { -+ json_object_array_add(val_list, json_object_new_string(mods[j]->mod_bvalues[i]->bv_val)); -+ } - } - json_object_object_add(mod, "values", val_list); - } -@@ -514,8 +624,11 @@ write_audit_file_json(Slapi_PBlock *pb, Slapi_Entry *entry, int logtype, - - case SLAPI_OPERATION_ADD: - int len; -+ - e = change; -- tmp = slapi_entry2str(e, &len); -+ -+ /* Create a masked string representation for password attributes */ -+ tmp = create_masked_entry_string(e, &len); - tmpsave = tmp; - while ((tmp = strchr(tmp, '\n')) != NULL) { - tmp++; -@@ -662,6 +775,10 @@ write_audit_file( - break; - } - } -+ -+ /* Check if this is a password attribute that needs masking */ -+ int is_password_attr = is_password_attribute(mods[j]->mod_type); -+ - switch (operationtype) { - case LDAP_MOD_ADD: - addlenstr(l, "add: "); -@@ -686,18 +803,27 @@ write_audit_file( - break; - } - if (operationtype != LDAP_MOD_IGNORE) { -- for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) { -- char *buf, *bufp; -- len = strlen(mods[j]->mod_type); -- len = LDIF_SIZE_NEEDED(len, mods[j]->mod_bvalues[i]->bv_len) + 1; -- buf = slapi_ch_malloc(len); -- bufp = buf; -- slapi_ldif_put_type_and_value_with_options(&bufp, mods[j]->mod_type, -- mods[j]->mod_bvalues[i]->bv_val, -- mods[j]->mod_bvalues[i]->bv_len, 0); -- *bufp = '\0'; -- addlenstr(l, buf); -- slapi_ch_free((void **)&buf); -+ if (is_password_attr) { -+ /* Add masked password */ -+ for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) { -+ addlenstr(l, mods[j]->mod_type); -+ addlenstr(l, ": **********************\n"); -+ } -+ } else { -+ /* Add actual values for non-password attributes */ -+ for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) { -+ char *buf, *bufp; -+ len = strlen(mods[j]->mod_type); -+ len = LDIF_SIZE_NEEDED(len, mods[j]->mod_bvalues[i]->bv_len) + 1; -+ buf = slapi_ch_malloc(len); -+ bufp = buf; -+ slapi_ldif_put_type_and_value_with_options(&bufp, mods[j]->mod_type, -+ mods[j]->mod_bvalues[i]->bv_val, -+ mods[j]->mod_bvalues[i]->bv_len, 0); -+ *bufp = '\0'; -+ addlenstr(l, buf); -+ slapi_ch_free((void **)&buf); -+ } - } - } - addlenstr(l, "-\n"); -@@ -708,7 +834,7 @@ write_audit_file( - e = change; - addlenstr(l, attr_changetype); - addlenstr(l, ": add\n"); -- tmp = slapi_entry2str(e, &len); -+ tmp = create_masked_entry_string(e, &len); - tmpsave = tmp; - while ((tmp = strchr(tmp, '\n')) != NULL) { - tmp++; -diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h -index 7a3eb3fdf..fb88488b1 100644 ---- a/ldap/servers/slapd/slapi-private.h -+++ b/ldap/servers/slapd/slapi-private.h -@@ -848,6 +848,7 @@ void task_cleanup(void); - /* for reversible encyrption */ - #define SLAPI_MB_CREDENTIALS "nsmultiplexorcredentials" - #define SLAPI_REP_CREDENTIALS "nsds5ReplicaCredentials" -+#define SLAPI_REP_BOOTSTRAP_CREDENTIALS "nsds5ReplicaBootstrapCredentials" - int pw_rever_encode(Slapi_Value **vals, char *attr_name); - int pw_rever_decode(char *cipher, char **plain, const char *attr_name); - -diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py -index 533b83ebf..33ae78c8b 100644 ---- a/src/lib389/lib389/chaining.py -+++ b/src/lib389/lib389/chaining.py -@@ -134,7 +134,7 @@ class ChainingLink(DSLdapObject): - """ - - # Create chaining entry -- super(ChainingLink, self).create(rdn, properties, basedn) -+ link = super(ChainingLink, self).create(rdn, properties, basedn) - - # Create mapping tree entry - dn_comps = ldap.explode_dn(properties['nsslapd-suffix'][0]) -@@ -149,6 +149,7 @@ class ChainingLink(DSLdapObject): - self._mts.ensure_state(properties=mt_properties) - except ldap.ALREADY_EXISTS: - pass -+ return link - - - class ChainingLinks(DSLdapObjects): --- -2.49.0 - diff --git a/0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch b/0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch deleted file mode 100644 index e9a2e57..0000000 --- a/0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch +++ /dev/null @@ -1,1721 +0,0 @@ -From 590c11b8fb24dde31910614eff1810e2eb0377a9 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Mon, 28 Jul 2025 20:02:09 -0700 -Subject: [PATCH] Issue 6897 - Fix disk monitoring test failures and improve - test maintainability (#6898) - -Description: Refactor disk_monitoring_test.py to address failures and -improve maintainability. Replace manual sleep loops with proper wait -conditions using wait_for_condition() and wait_for_log_entry() helpers. -Add comprehensive logging throughout all tests for better debugging. -Implement configuration capture/restore to prevent test pollution -between runs. -Change fixture scope from module to function level for better test -isolation and ensure proper cleanup in all test cases. - -Fixes: https://github.com/389ds/389-ds-base/issues/6897 - -Reviewed by: @mreynolds389 (Thanks!) ---- - .../disk_monitoring/disk_monitoring_test.py | 1429 +++++++++++------ - 1 file changed, 940 insertions(+), 489 deletions(-) - -diff --git a/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_test.py b/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_test.py -index 78d7dd794..a4c445748 100644 ---- a/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_test.py -+++ b/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_test.py -@@ -1,17 +1,18 @@ - # --- BEGIN COPYRIGHT BLOCK --- --# Copyright (C) 2018 Red Hat, Inc. -+# Copyright (C) 2025 Red Hat, Inc. - # All rights reserved. - # - # License: GPL (version 3 or any later version). - # See LICENSE for details. - # --- END COPYRIGHT BLOCK --- - -- - import os - import subprocess - import re - import time -+import ldap - import pytest -+import logging - from lib389.tasks import * - from lib389._constants import * - from lib389.utils import ensure_bytes -@@ -20,95 +21,221 @@ from lib389.topologies import topology_st as topo - from lib389.paths import * - from lib389.idm.user import UserAccounts - -+DEBUGGING = os.getenv("DEBUGGING", default=False) -+if DEBUGGING: -+ logging.getLogger(__name__).setLevel(logging.DEBUG) -+else: -+ logging.getLogger(__name__).setLevel(logging.INFO) -+log = logging.getLogger(__name__) -+ - pytestmark = pytest.mark.tier2 - disk_monitoring_ack = pytest.mark.skipif(not os.environ.get('DISK_MONITORING_ACK', False), reason="Disk monitoring tests may damage system configuration.") - --THRESHOLD = '30' --THRESHOLD_BYTES = '30000000' -+THRESHOLD_BYTES = 30000000 - - --def _withouterrorlog(topo, condition, maxtimesleep): -- timecount = 0 -- while eval(condition): -- time.sleep(1) -- timecount += 1 -- if timecount >= maxtimesleep: break -- assert not eval(condition) -+def presetup(inst): -+ """Presetup function to mount a tmpfs for log directory to simulate disk space limits.""" - -+ log.info("Setting up tmpfs for disk monitoring tests") -+ inst.stop() -+ log_dir = inst.ds_paths.log_dir - --def _witherrorlog(topo, condition, maxtimesleep): -- timecount = 0 -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- while condition not in study: -- time.sleep(1) -- timecount += 1 -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- if timecount >= maxtimesleep: break -- assert condition in study -+ if os.path.exists(log_dir): -+ log.debug(f"Mounting tmpfs on existing directory: {log_dir}") -+ subprocess.call(['mount', '-t', 'tmpfs', '-o', 'size=35M', 'tmpfs', log_dir]) -+ else: -+ log.debug(f"Creating and mounting tmpfs on new directory: {log_dir}") -+ os.mkdir(log_dir) -+ subprocess.call(['mount', '-t', 'tmpfs', '-o', 'size=35M', 'tmpfs', log_dir]) -+ -+ subprocess.call(f'chown {DEFAULT_USER}: -R {log_dir}', shell=True) -+ subprocess.call(f'chown {DEFAULT_USER}: -R {log_dir}/*', shell=True) -+ subprocess.call(f'restorecon -FvvR {log_dir}', shell=True) -+ inst.start() -+ log.info("tmpfs setup completed") -+ -+ -+def setupthesystem(inst): -+ """Setup system configuration for disk monitoring tests.""" -+ -+ log.info("Configuring system for disk monitoring tests") -+ inst.start() -+ inst.config.set('nsslapd-disk-monitoring-grace-period', '1') -+ inst.config.set('nsslapd-accesslog-logbuffering', 'off') -+ inst.config.set('nsslapd-disk-monitoring-threshold', ensure_bytes(str(THRESHOLD_BYTES))) -+ inst.restart() -+ log.info("System configuration completed") -+ -+ -+def capture_config(inst): -+ """Capture current configuration values for later restoration.""" -+ -+ log.info("Capturing current configuration values") -+ -+ config_attrs = [ -+ 'nsslapd-disk-monitoring', -+ 'nsslapd-disk-monitoring-threshold', -+ 'nsslapd-disk-monitoring-grace-period', -+ 'nsslapd-disk-monitoring-logging-critical', -+ 'nsslapd-disk-monitoring-readonly-on-threshold', -+ 'nsslapd-accesslog-logbuffering', -+ 'nsslapd-errorlog-level', -+ 'nsslapd-accesslog-logging-enabled', -+ 'nsslapd-accesslog-maxlogsize', -+ 'nsslapd-accesslog-logrotationtimeunit', -+ 'nsslapd-accesslog-level', -+ 'nsslapd-external-libs-debug-enabled', -+ 'nsslapd-errorlog-logging-enabled' -+ ] -+ -+ captured_config = {} -+ for config_attr in config_attrs: -+ try: -+ current_value = inst.config.get_attr_val_utf8(config_attr) -+ captured_config[config_attr] = current_value -+ log.debug(f"Captured {config_attr}: {current_value}") -+ except Exception as e: -+ log.debug(f"Could not capture {config_attr}: {e}") -+ captured_config[config_attr] = None - -+ log.info("Configuration capture completed") -+ return captured_config - --def presetup(topo): -- """ -- This is function is part of fixture function setup , will setup the environment for this test. -- """ -- topo.standalone.stop() -- if os.path.exists(topo.standalone.ds_paths.log_dir): -- subprocess.call(['mount', '-t', 'tmpfs', '-o', 'size=35M', 'tmpfs', topo.standalone.ds_paths.log_dir]) -- else: -- os.mkdir(topo.standalone.ds_paths.log_dir) -- subprocess.call(['mount', '-t', 'tmpfs', '-o', 'size=35M', 'tmpfs', topo.standalone.ds_paths.log_dir]) -- subprocess.call('chown {}: -R {}'.format(DEFAULT_USER, topo.standalone.ds_paths.log_dir), shell=True) -- subprocess.call('chown {}: -R {}/*'.format(DEFAULT_USER, topo.standalone.ds_paths.log_dir), shell=True) -- subprocess.call('restorecon -FvvR {}'.format(topo.standalone.ds_paths.log_dir), shell=True) -- topo.standalone.start() - -+def restore_config(inst, captured_config): -+ """Restore configuration values to previously captured state.""" - --def setupthesystem(topo): -- """ -- This function is part of fixture function setup , will setup the environment for this test. -- """ -- global TOTAL_SIZE, USED_SIZE, AVAIL_SIZE, HALF_THR_FILL_SIZE, FULL_THR_FILL_SIZE -- topo.standalone.start() -- topo.standalone.config.set('nsslapd-disk-monitoring-grace-period', '1') -- topo.standalone.config.set('nsslapd-accesslog-logbuffering', 'off') -- topo.standalone.config.set('nsslapd-disk-monitoring-threshold', ensure_bytes(THRESHOLD_BYTES)) -- TOTAL_SIZE = int(re.findall(r'\d+', str(os.statvfs(topo.standalone.ds_paths.log_dir)))[2])*4096/1024/1024 -- AVAIL_SIZE = round(int(re.findall(r'\d+', str(os.statvfs(topo.standalone.ds_paths.log_dir)))[3]) * 4096 / 1024 / 1024) -- USED_SIZE = TOTAL_SIZE - AVAIL_SIZE -- HALF_THR_FILL_SIZE = TOTAL_SIZE - float(THRESHOLD) + 5 - USED_SIZE -- FULL_THR_FILL_SIZE = TOTAL_SIZE - 0.5 * float(THRESHOLD) + 5 - USED_SIZE -- HALF_THR_FILL_SIZE = round(HALF_THR_FILL_SIZE) -- FULL_THR_FILL_SIZE = round(FULL_THR_FILL_SIZE) -- topo.standalone.restart() -- -- --@pytest.fixture(scope="module") -+ log.info("Restoring configuration to captured values") -+ -+ for config_attr, original_value in captured_config.items(): -+ if original_value is not None: -+ try: -+ current_value = inst.config.get_attr_val_utf8(config_attr) -+ if current_value != original_value: -+ log.debug(f"Restoring {config_attr} from '{current_value}' to '{original_value}'") -+ inst.config.set(config_attr, ensure_bytes(original_value)) -+ except Exception as e: -+ log.debug(f"Could not restore {config_attr}: {e}") -+ -+ log.info("Configuration restoration completed") -+ -+ -+@pytest.fixture(scope="function") - def setup(request, topo): -- """ -- This is the fixture function , will run before running every test case. -- """ -- presetup(topo) -- setupthesystem(topo) -+ """Module-level fixture to setup the test environment.""" -+ -+ log.info("Starting module setup for disk monitoring tests") -+ inst = topo.standalone -+ -+ # Capture current configuration before making any changes -+ original_config = capture_config(inst) -+ -+ presetup(inst) -+ setupthesystem(inst) - - def fin(): -- topo.standalone.stop() -- subprocess.call(['umount', '-fl', topo.standalone.ds_paths.log_dir]) -- topo.standalone.start() -+ log.info("Running module cleanup for disk monitoring tests") -+ inst.stop() -+ subprocess.call(['umount', '-fl', inst.ds_paths.log_dir]) -+ # Restore configuration to original values -+ inst.start() -+ restore_config(inst, original_config) -+ log.info("Module cleanup completed") - - request.addfinalizer(fin) - - -+def wait_for_condition(inst, condition_str, timeout=30): -+ """Wait until the given condition evaluates to False.""" -+ -+ log.debug(f"Waiting for condition to be False: {condition_str} (timeout: {timeout}s)") -+ start_time = time.time() -+ while time.time() - start_time < timeout: -+ if not eval(condition_str): -+ log.debug(f"Condition satisfied after {time.time() - start_time:.2f}s") -+ return -+ time.sleep(1) -+ raise AssertionError(f"Condition '{condition_str}' still True after {timeout} seconds") -+ -+ -+def wait_for_log_entry(inst, message, timeout=30): -+ """Wait for a specific message to appear in the error log.""" -+ -+ log.debug(f"Waiting for log entry: '{message}' (timeout: {timeout}s)") -+ start_time = time.time() -+ while time.time() - start_time < timeout: -+ with open(inst.errlog, 'r') as log_file: -+ if message in log_file.read(): -+ log.debug(f"Found log entry after {time.time() - start_time:.2f}s") -+ return -+ time.sleep(1) -+ raise AssertionError(f"Message '{message}' not found in error log after {timeout} seconds") -+ -+ -+def get_avail_bytes(path): -+ """Get available bytes on the filesystem at the given path.""" -+ -+ stat = os.statvfs(path) -+ return stat.f_bavail * stat.f_bsize -+ -+ -+def fill_to_target_avail(path, target_avail_bytes): -+ """Fill the disk to reach the target available bytes by creating a large file.""" -+ -+ avail = get_avail_bytes(path) -+ fill_bytes = avail - target_avail_bytes -+ log.debug(f"Current available: {avail}, target: {target_avail_bytes}, will create {fill_bytes} byte file") -+ if fill_bytes <= 0: -+ raise ValueError("Already below target avail") -+ -+ fill_file = os.path.join(path, 'fill.dd') -+ bs = 4096 -+ count = (fill_bytes + bs - 1) // bs # ceil division to ensure enough -+ log.info(f"Creating fill file {fill_file} with {count} blocks of {bs} bytes") -+ subprocess.check_call(['dd', 'if=/dev/zero', f'of={fill_file}', f'bs={bs}', f'count={count}']) -+ return fill_file -+ -+ - @pytest.fixture(scope="function") - def reset_logs(topo): -- """ -- Reset the errors log file before the test -- """ -- open('{}/errors'.format(topo.standalone.ds_paths.log_dir), 'w').close() -+ """Function-level fixture to reset the error log before each test.""" -+ -+ log.debug("Resetting error logs before test") -+ topo.standalone.deleteErrorLogs() -+ -+ -+def generate_access_log_activity(inst, num_users=10, num_binds=100): -+ """Generate access log activity by creating users and performing binds.""" -+ -+ log.info(f"Generating access log activity with {num_users} users and {num_binds} binds each") -+ users = UserAccounts(inst, DEFAULT_SUFFIX) -+ -+ # Create test users -+ for i in range(num_users): -+ user_properties = { -+ 'uid': f'cn=user{i}', -+ 'cn': f'cn=user{i}', -+ 'sn': f'cn=user{i}', -+ 'userPassword': "Itsme123", -+ 'uidNumber': f'1{i}', -+ 'gidNumber': f'2{i}', -+ 'homeDirectory': f'/home/{i}' -+ } -+ users.create(properties=user_properties) -+ -+ # Perform bind operations -+ for j in range(num_binds): -+ for user in users.list(): -+ user.bind('Itsme123') -+ -+ log.info("Access log activity generation completed") -+ return users - - - @disk_monitoring_ack - def test_verify_operation_when_disk_monitoring_is_off(topo, setup, reset_logs): -- """Verify operation when Disk monitoring is off -+ """Verify operation when Disk monitoring is off. - - :id: 73a97536-fe9e-11e8-ba9f-8c16451d917b - :setup: Standalone -@@ -117,94 +244,127 @@ def test_verify_operation_when_disk_monitoring_is_off(topo, setup, reset_logs): - 2. Go below the threshold - 3. Check DS is up and not entering shutdown mode - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -+ 1. Success -+ 2. Success -+ 3. Success - """ -+ log.info("Starting test_verify_operation_when_disk_monitoring_is_off") -+ inst = topo.standalone -+ fill_file = None -+ - try: -- # Turn off disk monitoring -- topo.standalone.config.set('nsslapd-disk-monitoring', 'off') -- topo.standalone.restart() -- # go below the threshold -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo1'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- # Wait for disk monitoring plugin thread to wake up -- _withouterrorlog(topo, 'topo.standalone.status() != True', 10) -+ log.info("Disabling disk monitoring") -+ inst.config.set('nsslapd-disk-monitoring', 'off') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below threshold ({THRESHOLD_BYTES} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES - 1) -+ -+ log.info("Verifying server stays up despite being below threshold") -+ wait_for_condition(inst, 'inst.status() != True', 11) -+ - # Check DS is up and not entering shutdown mode -- assert topo.standalone.status() == True -+ assert inst.status() == True -+ log.info("Verified: server remains operational when disk monitoring is disabled") -+ - finally: -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -- os.remove('{}/foo1'.format(topo.standalone.ds_paths.log_dir)) -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_enable_external_libs_debug_log(topo, setup, reset_logs): -- """Check that OpenLDAP logs are successfully enabled and disabled when -- disk threshold is reached -+ """Check that OpenLDAP logs are successfully enabled and disabled when disk threshold is reached. - - :id: 121b2b24-ecba-48e2-9ee2-312d929dc8c6 - :setup: Standalone instance -- :steps: 1. Set nsslapd-external-libs-debug-enabled to "on" -- 2. Go straight below 1/2 of the threshold -- 3. Verify that the external libs debug setting is disabled -- 4. Go back above 1/2 of the threshold -- 5. Verify that the external libs debug setting is enabled back -- :expectedresults: 1. Success -- 2. Success -- 3. Success -- 4. Success -- 5. Success -+ :steps: -+ 1. Set nsslapd-external-libs-debug-enabled to "on" -+ 2. Go straight below 1/2 of the threshold -+ 3. Verify that the external libs debug setting is disabled -+ 4. Go back above 1/2 of the threshold -+ 5. Verify that the external libs debug setting is enabled back -+ :expectedresults: -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Success - """ -+ log.info("Starting test_enable_external_libs_debug_log") -+ inst = topo.standalone -+ fill_file = None -+ - try: -- # Verify that verbose logging was set to default level -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -- assert topo.standalone.config.set('nsslapd-external-libs-debug-enabled', 'on') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(HALF_THR_FILL_SIZE)]) -- # Verify that logging is disabled -- _withouterrorlog(topo, "topo.standalone.config.get_attr_val_utf8('nsslapd-external-libs-debug-enabled') != 'off'", 31) -+ log.info("Configuring disk monitoring and external libs debug") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -+ inst.config.set('nsslapd-external-libs-debug-enabled', 'on') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below half threshold ({THRESHOLD_BYTES // 2} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ log.info("Verifying external libs debug is automatically disabled") -+ wait_for_condition(inst, "inst.config.get_attr_val_utf8('nsslapd-external-libs-debug-enabled') != 'off'", 31) -+ - finally: -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -- _withouterrorlog(topo, "topo.standalone.config.get_attr_val_utf8('nsslapd-external-libs-debug-enabled') != 'on'", 31) -- assert topo.standalone.config.set('nsslapd-external-libs-debug-enabled', 'off') -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Verifying external libs debug is re-enabled after freeing space") -+ wait_for_condition(inst, "inst.config.get_attr_val_utf8('nsslapd-external-libs-debug-enabled') != 'on'", 31) -+ inst.config.set('nsslapd-external-libs-debug-enabled', 'off') -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_free_up_the_disk_space_and_change_ds_config(topo, setup, reset_logs): -- """Free up the disk space and change DS config -+ """Free up the disk space and change DS config. - - :id: 7be4d560-fe9e-11e8-a307-8c16451d917b - :setup: Standalone - :steps: -- 1. Enabling Disk Monitoring plugin and setting disk monitoring logging to critical -+ 1. Enable Disk Monitoring plugin and set disk monitoring logging to critical - 2. Verify no message about loglevel is present in the error log - 3. Verify no message about disabling logging is present in the error log - 4. Verify no message about removing rotated logs is present in the error log - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -- 4. Should Success -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success - """ -- # Enabling Disk Monitoring plugin and setting disk monitoring logging to critical -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- # Verify no message about loglevel is present in the error log -- # Verify no message about disabling logging is present in the error log -- # Verify no message about removing rotated logs is present in the error log -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- assert 'temporarily setting error loglevel to zero' not in study -- assert 'disabling access and audit logging' not in study -- assert 'deleting rotated logs' not in study -+ log.info("Starting test_free_up_the_disk_space_and_change_ds_config") -+ inst = topo.standalone -+ -+ log.info("Enabling disk monitoring with critical logging") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ log.info("Verifying no premature disk monitoring messages in error log") -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ -+ assert 'temporarily setting error loglevel to zero' not in content -+ assert 'disabling access and audit logging' not in content -+ assert 'deleting rotated logs' not in content -+ -+ log.info("Verified: no unexpected disk monitoring messages found") -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_verify_operation_with_nsslapd_disk_monitoring_logging_critical_off(topo, setup, reset_logs): -- """Verify operation with "nsslapd-disk-monitoring-logging-critical: off -+ """Verify operation with "nsslapd-disk-monitoring-logging-critical: off". - - :id: 82363bca-fe9e-11e8-9ae7-8c16451d917b - :setup: Standalone -@@ -213,39 +373,59 @@ def test_verify_operation_with_nsslapd_disk_monitoring_logging_critical_off(topo - 2. Verify that logging is disabled - 3. Verify that rotated logs were not removed - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -+ 1. Success -+ 2. Success -+ 3. Success - """ -+ log.info("Starting test_verify_operation_with_nsslapd_disk_monitoring_logging_critical_off") -+ inst = topo.standalone -+ fill_file = None -+ - try: -- # Verify that verbose logging was set to default level -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(HALF_THR_FILL_SIZE)]) -- _witherrorlog(topo, 'temporarily setting error loglevel to the default level', 11) -- assert LOG_DEFAULT == int(re.findall(r'nsslapd-errorlog-level: \d+', str( -- topo.standalone.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-errorlog-level'])))[ -- 0].split(' ')[1]) -- # Verify that logging is disabled -- _withouterrorlog(topo, "topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'off'", 10) -- assert topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') == 'off' -- # Verify that rotated logs were not removed -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- assert 'disabling access and audit logging' in study -- _witherrorlog(topo, 'deleting rotated logs', 11) -- study = open(topo.standalone.errlog).read() -- assert "Unable to remove file: {}".format(topo.standalone.ds_paths.log_dir) not in study -- assert 'is too far below the threshold' not in study -+ log.info("Configuring disk monitoring with critical logging disabled") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below threshold ({THRESHOLD_BYTES} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES - 1) -+ -+ log.info("Waiting for loglevel to be set to default") -+ wait_for_log_entry(inst, 'temporarily setting error loglevel to the default level', 11) -+ -+ log.info("Verifying error log level was set to default") -+ config_entry = inst.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-errorlog-level']) -+ current_level = int(re.findall(r'nsslapd-errorlog-level: \d+', str(config_entry))[0].split(' ')[1]) -+ assert LOG_DEFAULT == current_level -+ -+ log.info("Verifying access logging is disabled") -+ wait_for_condition(inst, "inst.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'off'", 11) -+ assert inst.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') == 'off' -+ -+ log.info("Verifying expected disk monitoring messages") -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ -+ assert 'disabling access and audit logging' in content -+ wait_for_log_entry(inst, 'deleting rotated logs', 11) -+ assert f"Unable to remove file: {inst.ds_paths.log_dir}" not in content -+ assert 'is too far below the threshold' not in content -+ -+ log.info("All verifications passed") -+ - finally: -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_operation_with_nsslapd_disk_monitoring_logging_critical_on_below_half_of_the_threshold(topo, setup, reset_logs): -- """Verify operation with \"nsslapd-disk-monitoring-logging-critical: on\" below 1/2 of the threshold -- Verify recovery -+ """Verify operation with "nsslapd-disk-monitoring-logging-critical: on" below 1/2 of the threshold. -+ Verify recovery. - - :id: 8940c502-fe9e-11e8-bcc0-8c16451d917b - :setup: Standalone -@@ -253,190 +433,277 @@ def test_operation_with_nsslapd_disk_monitoring_logging_critical_on_below_half_o - 1. Verify that DS goes into shutdown mode - 2. Verify that DS exited shutdown mode - :expectedresults: -- 1. Should Success -- 2. Should Success -+ 1. Success -+ 2. Success - """ -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -- topo.standalone.restart() -- # Verify that DS goes into shutdown mode -- if float(THRESHOLD) > FULL_THR_FILL_SIZE: -- FULL_THR_FILL_SIZE_new = FULL_THR_FILL_SIZE + round(float(THRESHOLD) - FULL_THR_FILL_SIZE) + 1 -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE_new)]) -- else: -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- _witherrorlog(topo, 'is too far below the threshold', 20) -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -- # Verify that DS exited shutdown mode -- _witherrorlog(topo, 'Available disk space is now acceptable', 25) -+ log.info("Starting test_operation_with_nsslapd_disk_monitoring_logging_critical_on_below_half_of_the_threshold") -+ inst = topo.standalone -+ fill_file = None -+ -+ try: -+ log.info("Configuring disk monitoring with critical logging enabled") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below half threshold ({THRESHOLD_BYTES // 2} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ log.info("Waiting for shutdown mode message") -+ wait_for_log_entry(inst, 'is too far below the threshold', 100) -+ -+ log.info("Freeing up disk space") -+ os.remove(fill_file) -+ fill_file = None -+ -+ log.info("Waiting for recovery message") -+ wait_for_log_entry(inst, 'Available disk space is now acceptable', 25) -+ -+ log.info("Verified: server entered and exited shutdown mode correctly") -+ -+ finally: -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_setting_nsslapd_disk_monitoring_logging_critical_to_off(topo, setup, reset_logs): -- """Setting nsslapd-disk-monitoring-logging-critical to "off" -+ """Setting nsslapd-disk-monitoring-logging-critical to "off". - - :id: 93265ec4-fe9e-11e8-af93-8c16451d917b - :setup: Standalone - :steps: -- 1. Setting nsslapd-disk-monitoring-logging-critical to "off" -+ 1. Set nsslapd-disk-monitoring-logging-critical to "off" - :expectedresults: -- 1. Should Success -+ 1. Success - """ -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- assert topo.standalone.status() == True -+ log.info("Starting test_setting_nsslapd_disk_monitoring_logging_critical_to_off") -+ inst = topo.standalone -+ -+ log.info("Setting disk monitoring configuration") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ log.info("Verifying server is running normally") -+ assert inst.status() == True -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_operation_with_nsslapd_disk_monitoring_logging_critical_off(topo, setup, reset_logs): -- """Verify operation with nsslapd-disk-monitoring-logging-critical: off -+ """Verify operation with nsslapd-disk-monitoring-logging-critical: off. - - :id: 97985a52-fe9e-11e8-9914-8c16451d917b - :setup: Standalone - :steps: -- 1. Verify that logging is disabled -- 2. Verify that rotated logs were removed -+ 1. Generate access log activity to create rotated logs -+ 2. Go below threshold to trigger disk monitoring - 3. Verify that verbose logging was set to default level - 4. Verify that logging is disabled - 5. Verify that rotated logs were removed - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -- 4. Should Success -- 5. Should Success -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Success - """ -- # Verify that logging is disabled -+ log.info("Starting test_operation_with_nsslapd_disk_monitoring_logging_critical_off") -+ inst = topo.standalone -+ fill_file = None -+ users = None -+ - try: -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- assert topo.standalone.config.set('nsslapd-accesslog-maxlogsize', '1') -- assert topo.standalone.config.set('nsslapd-accesslog-logrotationtimeunit', 'minute') -- assert topo.standalone.config.set('nsslapd-accesslog-level', '772') -- topo.standalone.restart() -- # Verify that rotated logs were removed -- users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) -- for i in range(10): -- user_properties = { -- 'uid': 'cn=anuj{}'.format(i), -- 'cn': 'cn=anuj{}'.format(i), -- 'sn': 'cn=anuj{}'.format(i), -- 'userPassword': "Itsme123", -- 'uidNumber': '1{}'.format(i), -- 'gidNumber': '2{}'.format(i), -- 'homeDirectory': '/home/{}'.format(i) -- } -- users.create(properties=user_properties) -- for j in range(100): -- for i in [i for i in users.list()]: i.bind('Itsme123') -- assert re.findall(r'access.\d+-\d+',str(os.listdir(topo.standalone.ds_paths.log_dir))) -- topo.standalone.bind_s(DN_DM, PW_DM) -- assert topo.standalone.config.set('nsslapd-accesslog-maxlogsize', '100') -- assert topo.standalone.config.set('nsslapd-accesslog-logrotationtimeunit', 'day') -- assert topo.standalone.config.set('nsslapd-accesslog-level', '256') -- topo.standalone.restart() -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo2'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(HALF_THR_FILL_SIZE)]) -- # Verify that verbose logging was set to default level -- _witherrorlog(topo, 'temporarily setting error loglevel to the default level', 10) -- assert LOG_DEFAULT == int(re.findall(r'nsslapd-errorlog-level: \d+', str( -- topo.standalone.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-errorlog-level'])))[0].split(' ')[1]) -- # Verify that logging is disabled -- _withouterrorlog(topo, "topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'off'", 20) -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- assert 'disabling access and audit logging' in study -- # Verify that rotated logs were removed -- _witherrorlog(topo, 'deleting rotated logs', 10) -- with open(topo.standalone.errlog, 'r') as study:study = study.read() -- assert 'Unable to remove file:' not in study -- assert 'is too far below the threshold' not in study -- for i in [i for i in users.list()]: i.delete() -+ log.info("Configuring disk monitoring and access log settings") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.config.set('nsslapd-accesslog-maxlogsize', '1') -+ inst.config.set('nsslapd-accesslog-logrotationtimeunit', 'minute') -+ inst.config.set('nsslapd-accesslog-level', '772') -+ inst.restart() -+ -+ log.info("Generating access log activity to create rotated logs") -+ users = generate_access_log_activity(inst, num_users=10, num_binds=100) -+ -+ inst.bind_s(DN_DM, PW_DM) -+ -+ log.info("Resetting access log settings") -+ inst.config.set('nsslapd-accesslog-maxlogsize', '100') -+ inst.config.set('nsslapd-accesslog-logrotationtimeunit', 'day') -+ inst.config.set('nsslapd-accesslog-level', '256') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below threshold ({THRESHOLD_BYTES} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES - 1) -+ -+ log.info("Waiting for loglevel to be set to default") -+ wait_for_log_entry(inst, 'temporarily setting error loglevel to the default level', 11) -+ -+ log.info("Verifying error log level was set to default") -+ config_level = None -+ for _ in range(10): -+ time.sleep(1) -+ config_entry = inst.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-errorlog-level']) -+ config_level = int(re.findall(r'nsslapd-errorlog-level: \d+', str(config_entry))[0].split(' ')[1]) -+ if LOG_DEFAULT == config_level: -+ break -+ assert LOG_DEFAULT == config_level -+ -+ log.info("Verifying access logging is disabled") -+ wait_for_condition(inst, "inst.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') == 'off'", 20) -+ -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ assert 'disabling access and audit logging' in content -+ -+ log.info("Verifying rotated logs are removed") -+ wait_for_log_entry(inst, 'deleting rotated logs', 20) -+ -+ rotated_logs = re.findall(r'access.\d+-\d+', str(os.listdir(inst.ds_paths.log_dir))) -+ assert not rotated_logs, f"Found unexpected rotated logs: {rotated_logs}" -+ -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ assert 'Unable to remove file:' not in content -+ assert 'is too far below the threshold' not in content -+ -+ log.info("All verifications passed") -+ - finally: -- os.remove('{}/foo2'.format(topo.standalone.ds_paths.log_dir)) -+ # Clean up users -+ if users: -+ log.debug("Cleaning up test users") -+ for user in users.list(): -+ try: -+ user.delete() -+ except ldap.ALREADY_EXISTS: -+ pass -+ -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_operation_with_nsslapd_disk_monitoring_logging_critical_off_below_half_of_the_threshold(topo, setup, reset_logs): -- """Verify operation with nsslapd-disk-monitoring-logging-critical: off below 1/2 of the threshold -- Verify shutdown -- Recovery and setup -+ """Verify operation with nsslapd-disk-monitoring-logging-critical: off below 1/2 of the threshold. -+ Verify shutdown and recovery. - - :id: 9d4c7d48-fe9e-11e8-b5d6-8c16451d917b - :setup: Standalone - :steps: -- 1. Verify that DS goes into shutdown mode -- 2. Verifying that DS has been shut down after the grace period -- 3. Verify logging enabled -- 4. Create rotated logfile -- 5. Enable verbose logging -+ 1. Go below half threshold to trigger shutdown -+ 2. Verify DS shutdown after grace period -+ 3. Free space and restart -+ 4. Verify logging is re-enabled -+ 5. Create rotated logs and enable verbose logging - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -- 4. Should Success -- 5. Should Success -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Success - """ -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -- topo.standalone.restart() -- # Verify that DS goes into shutdown mode -- if float(THRESHOLD) > FULL_THR_FILL_SIZE: -- FULL_THR_FILL_SIZE_new = FULL_THR_FILL_SIZE + round(float(THRESHOLD) - FULL_THR_FILL_SIZE) -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE_new)]) -- else: -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- # Increased sleep to avoid failure -- _witherrorlog(topo, 'is too far below the threshold', 100) -- _witherrorlog(topo, 'Signaling slapd for shutdown', 90) -- # Verifying that DS has been shut down after the grace period -- time.sleep(2) -- assert topo.standalone.status() == False -- # free_space -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -- open('{}/errors'.format(topo.standalone.ds_paths.log_dir), 'w').close() -- # StartSlapd -- topo.standalone.start() -- # verify logging enabled -- assert topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') == 'on' -- assert topo.standalone.config.get_attr_val_utf8('nsslapd-errorlog-logging-enabled') == 'on' -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- assert 'disabling access and audit logging' not in study -- assert topo.standalone.config.set('nsslapd-accesslog-maxlogsize', '1') -- assert topo.standalone.config.set('nsslapd-accesslog-logrotationtimeunit', 'minute') -- assert topo.standalone.config.set('nsslapd-accesslog-level', '772') -- topo.standalone.restart() -- # create rotated logfile -- users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) -- for i in range(10): -- user_properties = { -- 'uid': 'cn=anuj{}'.format(i), -- 'cn': 'cn=anuj{}'.format(i), -- 'sn': 'cn=anuj{}'.format(i), -- 'userPassword': "Itsme123", -- 'uidNumber': '1{}'.format(i), -- 'gidNumber': '2{}'.format(i), -- 'homeDirectory': '/home/{}'.format(i) -- } -- users.create(properties=user_properties) -- for j in range(100): -- for i in [i for i in users.list()]: i.bind('Itsme123') -- assert re.findall(r'access.\d+-\d+',str(os.listdir(topo.standalone.ds_paths.log_dir))) -- topo.standalone.bind_s(DN_DM, PW_DM) -- # enable verbose logging -- assert topo.standalone.config.set('nsslapd-accesslog-maxlogsize', '100') -- assert topo.standalone.config.set('nsslapd-accesslog-logrotationtimeunit', 'day') -- assert topo.standalone.config.set('nsslapd-accesslog-level', '256') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- for i in [i for i in users.list()]: i.delete() -+ log.info("Starting test_operation_with_nsslapd_disk_monitoring_logging_critical_off_below_half_of_the_threshold") -+ inst = topo.standalone -+ fill_file = None -+ users = None -+ -+ try: -+ log.info("Configuring disk monitoring with critical logging disabled") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below half threshold ({THRESHOLD_BYTES // 2} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ log.info("Waiting for shutdown messages") -+ wait_for_log_entry(inst, 'is too far below the threshold', 100) -+ wait_for_log_entry(inst, 'Signaling slapd for shutdown', 90) -+ -+ log.info("Verifying server shutdown within grace period") -+ for i in range(60): -+ time.sleep(1) -+ if not inst.status(): -+ log.info(f"Server shut down after {i+1} seconds") -+ break -+ assert inst.status() == False -+ -+ log.info("Freeing disk space and cleaning logs") -+ os.remove(fill_file) -+ fill_file = None -+ open(f'{inst.ds_paths.log_dir}/errors', 'w').close() -+ -+ log.info("Starting server after freeing space") -+ inst.start() -+ -+ log.info("Verifying logging is re-enabled") -+ assert inst.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') == 'on' -+ assert inst.config.get_attr_val_utf8('nsslapd-errorlog-logging-enabled') == 'on' -+ -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ assert 'disabling access and audit logging' not in content -+ -+ log.info("Setting up access log rotation for testing") -+ inst.config.set('nsslapd-accesslog-maxlogsize', '1') -+ inst.config.set('nsslapd-accesslog-logrotationtimeunit', 'minute') -+ inst.config.set('nsslapd-accesslog-level', '772') -+ inst.restart() -+ -+ log.info("Creating rotated log files through user activity") -+ users = generate_access_log_activity(inst, num_users=10, num_binds=100) -+ -+ log.info("Waiting for log rotation to occur") -+ for i in range(61): -+ time.sleep(1) -+ rotated_logs = re.findall(r'access.\d+-\d+', str(os.listdir(inst.ds_paths.log_dir))) -+ if rotated_logs: -+ log.info(f"Log rotation detected after {i+1} seconds") -+ break -+ assert rotated_logs, "No rotated logs found after waiting" -+ -+ inst.bind_s(DN_DM, PW_DM) -+ -+ log.info("Enabling verbose logging") -+ inst.config.set('nsslapd-accesslog-maxlogsize', '100') -+ inst.config.set('nsslapd-accesslog-logrotationtimeunit', 'day') -+ inst.config.set('nsslapd-accesslog-level', '256') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ log.info("Recovery and setup verification completed") -+ -+ finally: -+ # Clean up users -+ if users: -+ log.debug("Cleaning up test users") -+ for user in users.list(): -+ try: -+ user.delete() -+ except ldap.ALREADY_EXISTS: -+ pass -+ -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_go_straight_below_half_of_the_threshold(topo, setup, reset_logs): -- """Go straight below 1/2 of the threshold -- Recovery and setup -+ """Go straight below 1/2 of the threshold and verify recovery. - - :id: a2a0664c-fe9e-11e8-b220-8c16451d917b - :setup: Standalone -@@ -447,252 +714,417 @@ def test_go_straight_below_half_of_the_threshold(topo, setup, reset_logs): - 4. Verify DS is in shutdown mode - 5. Verify DS has recovered from shutdown - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -- 4. Should Success -- 5. Should Success -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success -+ 5. Success - """ -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- if float(THRESHOLD) > FULL_THR_FILL_SIZE: -- FULL_THR_FILL_SIZE_new = FULL_THR_FILL_SIZE + round(float(THRESHOLD) - FULL_THR_FILL_SIZE) + 1 -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE_new)]) -- else: -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- _witherrorlog(topo, 'temporarily setting error loglevel to the default level', 11) -- # Verify that verbose logging was set to default level -- assert LOG_DEFAULT == int(re.findall(r'nsslapd-errorlog-level: \d+', -- str(topo.standalone.search_s('cn=config', ldap.SCOPE_SUBTREE, -- '(objectclass=*)', -- ['nsslapd-errorlog-level'])) -- )[0].split(' ')[1]) -- # Verify that logging is disabled -- _withouterrorlog(topo, "topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'off'", 11) -- # Verify that rotated logs were removed -- _witherrorlog(topo, 'disabling access and audit logging', 2) -- _witherrorlog(topo, 'deleting rotated logs', 11) -- with open(topo.standalone.errlog, 'r') as study:study = study.read() -- assert 'Unable to remove file:' not in study -- # Verify DS is in shutdown mode -- _withouterrorlog(topo, 'topo.standalone.status() != False', 90) -- _witherrorlog(topo, 'is too far below the threshold', 2) -- # Verify DS has recovered from shutdown -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -- open('{}/errors'.format(topo.standalone.ds_paths.log_dir), 'w').close() -- topo.standalone.start() -- _withouterrorlog(topo, "topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'on'", 20) -- with open(topo.standalone.errlog, 'r') as study: study = study.read() -- assert 'disabling access and audit logging' not in study -+ log.info("Starting test_go_straight_below_half_of_the_threshold") -+ inst = topo.standalone -+ fill_file = None -+ -+ try: -+ log.info("Configuring disk monitoring with critical logging disabled") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'off') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ # Go straight below half threshold -+ log.info(f"Filling disk to go below half threshold ({THRESHOLD_BYTES // 2} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ # Verify that verbose logging was set to default level -+ log.info("Waiting for loglevel to be set to default") -+ wait_for_log_entry(inst, 'temporarily setting error loglevel to the default level', 11) -+ -+ log.info("Verifying error log level was set to default") -+ config_entry = inst.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-errorlog-level']) -+ current_level = int(re.findall(r'nsslapd-errorlog-level: \d+', str(config_entry))[0].split(' ')[1]) -+ assert LOG_DEFAULT == current_level -+ -+ log.info("Verifying access logging is disabled") -+ wait_for_condition(inst, "inst.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'off'", 11) -+ -+ log.info("Verifying expected disk monitoring messages") -+ wait_for_log_entry(inst, 'disabling access and audit logging', 2) -+ wait_for_log_entry(inst, 'deleting rotated logs', 11) -+ -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ assert 'Unable to remove file:' not in content -+ -+ log.info("Verifying server enters shutdown mode") -+ wait_for_condition(inst, 'inst.status() != False', 90) -+ wait_for_log_entry(inst, 'is too far below the threshold', 2) -+ -+ log.info("Freeing disk space and restarting server") -+ os.remove(fill_file) -+ fill_file = None -+ open(f'{inst.ds_paths.log_dir}/errors', 'w').close() -+ inst.start() -+ -+ log.info("Verifying server recovery") -+ wait_for_condition(inst, "inst.config.get_attr_val_utf8('nsslapd-accesslog-logging-enabled') != 'on'", 20) -+ -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ assert 'disabling access and audit logging' not in content -+ -+ log.info("Recovery verification completed") -+ -+ finally: -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_readonly_on_threshold(topo, setup, reset_logs): -- """Verify that nsslapd-disk-monitoring-readonly-on-threshold switches the server to read-only mode -+ """Verify that nsslapd-disk-monitoring-readonly-on-threshold switches the server to read-only mode. - - :id: 06814c19-ef3c-4800-93c9-c7c6e76fcbb9 - :customerscenario: True - :setup: Standalone - :steps: -- 1. Verify that the backend is in read-only mode -- 2. Go back above the threshold -- 3. Verify that the backend is in read-write mode -+ 1. Configure readonly on threshold -+ 2. Go below threshold and verify backend is read-only -+ 3. Go back above threshold and verify backend is read-write - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -+ 1. Success -+ 2. Success -+ 3. Success - """ -- file_path = '{}/foo'.format(topo.standalone.ds_paths.log_dir) -- backends = Backends(topo.standalone) -- backend_name = backends.list()[0].rdn -- # Verify that verbose logging was set to default level -- topo.standalone.deleteErrorLogs() -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-readonly-on-threshold', 'on') -- topo.standalone.restart() -+ log.info("Starting test_readonly_on_threshold") -+ inst = topo.standalone -+ fill_file = None -+ test_user = None -+ - try: -- subprocess.call(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', f'count={HALF_THR_FILL_SIZE}']) -- _witherrorlog(topo, f"Putting the backend '{backend_name}' to read-only mode", 11) -+ backends = Backends(inst) -+ backend_name = backends.list()[0].rdn -+ log.info(f"Testing with backend: {backend_name}") -+ -+ log.info("Configuring disk monitoring with readonly on threshold") -+ inst.deleteErrorLogs() -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-readonly-on-threshold', 'on') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below threshold ({THRESHOLD_BYTES} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES - 1) -+ -+ log.info("Waiting for backend to enter read-only mode") -+ wait_for_log_entry(inst, f"Putting the backend '{backend_name}' to read-only mode", 11) -+ -+ log.info("Verifying backend is in read-only mode") - users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) - try: -- user = users.create_test_user() -- user.delete() -+ test_user = users.create_test_user() -+ test_user.delete() -+ assert False, "Expected UNWILLING_TO_PERFORM error for read-only mode" - except ldap.UNWILLING_TO_PERFORM as e: - if 'database is read-only' not in str(e): - raise -- os.remove(file_path) -- _witherrorlog(topo, f"Putting the backend '{backend_name}' back to read-write mode", 11) -- user = users.create_test_user() -- assert user.exists() -- user.delete() -+ log.info("Confirmed: backend correctly rejects writes in read-only mode") -+ -+ log.info("Freeing disk space") -+ os.remove(fill_file) -+ fill_file = None -+ -+ log.info("Waiting for backend to return to read-write mode") -+ wait_for_log_entry(inst, f"Putting the backend '{backend_name}' back to read-write mode", 11) -+ -+ log.info("Verifying backend is in read-write mode") -+ test_user = users.create_test_user() -+ assert test_user.exists() -+ test_user.delete() -+ test_user = None -+ -+ log.info("Confirmed: backend correctly accepts writes in read-write mode") -+ - finally: -- if os.path.exists(file_path): -- os.remove(file_path) -+ if test_user: -+ try: -+ test_user.delete() -+ except: -+ pass -+ -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_readonly_on_threshold_below_half_of_the_threshold(topo, setup, reset_logs): -- """Go below 1/2 of the threshold when readonly on threshold is enabled -+ """Go below 1/2 of the threshold when readonly on threshold is enabled. - - :id: 10262663-b41f-420e-a2d0-9532dd54fa7c - :customerscenario: True - :setup: Standalone - :steps: -- 1. Go straight below 1/2 of the threshold -- 2. Verify that the backend is in read-only mode -- 3. Go back above the threshold -- 4. Verify that the backend is in read-write mode -+ 1. Configure readonly on threshold -+ 2. Go below half threshold -+ 3. Verify backend is read-only and shutdown messages appear -+ 4. Free space and verify backend returns to read-write - :expectedresults: -- 1. Should Success -- 2. Should Success -- 3. Should Success -- 4. Should Success -+ 1. Success -+ 2. Success -+ 3. Success -+ 4. Success - """ -- file_path = '{}/foo'.format(topo.standalone.ds_paths.log_dir) -- backends = Backends(topo.standalone) -- backend_name = backends.list()[0].rdn -- topo.standalone.deleteErrorLogs() -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-readonly-on-threshold', 'on') -- topo.standalone.restart() -+ log.info("Starting test_readonly_on_threshold_below_half_of_the_threshold") -+ inst = topo.standalone -+ fill_file = None -+ test_user = None -+ - try: -- if float(THRESHOLD) > FULL_THR_FILL_SIZE: -- FULL_THR_FILL_SIZE_new = FULL_THR_FILL_SIZE + round(float(THRESHOLD) - FULL_THR_FILL_SIZE) + 1 -- subprocess.call(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', f'count={FULL_THR_FILL_SIZE_new}']) -- else: -- subprocess.call(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', f'count={FULL_THR_FILL_SIZE}']) -- _witherrorlog(topo, f"Putting the backend '{backend_name}' to read-only mode", 11) -- users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) -+ backends = Backends(inst) -+ backend_name = backends.list()[0].rdn -+ log.info(f"Testing with backend: {backend_name}") -+ -+ log.info("Configuring disk monitoring with readonly on threshold") -+ inst.deleteErrorLogs() -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-readonly-on-threshold', 'on') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below half threshold ({THRESHOLD_BYTES // 2} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ log.info("Waiting for backend to enter read-only mode") -+ wait_for_log_entry(inst, f"Putting the backend '{backend_name}' to read-only mode", 11) -+ -+ log.info("Verifying backend is in read-only mode") -+ users = UserAccounts(inst, DEFAULT_SUFFIX) - try: -- user = users.create_test_user() -- user.delete() -+ test_user = users.create_test_user() -+ test_user.delete() -+ assert False, "Expected UNWILLING_TO_PERFORM error for read-only mode" - except ldap.UNWILLING_TO_PERFORM as e: - if 'database is read-only' not in str(e): - raise -- _witherrorlog(topo, 'is too far below the threshold', 51) -- # Verify DS has recovered from shutdown -- os.remove(file_path) -- _witherrorlog(topo, f"Putting the backend '{backend_name}' back to read-write mode", 51) -- user = users.create_test_user() -- assert user.exists() -- user.delete() -+ log.info("Confirmed: backend correctly rejects writes in read-only mode") -+ -+ log.info("Waiting for shutdown threshold message") -+ wait_for_log_entry(inst, 'is too far below the threshold', 51) -+ -+ log.info("Freeing disk space") -+ os.remove(fill_file) -+ fill_file = None -+ -+ log.info("Waiting for backend to return to read-write mode") -+ wait_for_log_entry(inst, f"Putting the backend '{backend_name}' back to read-write mode", 51) -+ -+ log.info("Verifying backend is in read-write mode") -+ test_user = users.create_test_user() -+ assert test_user.exists() -+ test_user.delete() -+ test_user = None -+ -+ log.info("Confirmed: backend correctly accepts writes in read-write mode") -+ - finally: -- if os.path.exists(file_path): -- os.remove(file_path) -+ if test_user: -+ try: -+ test_user.delete() -+ except: -+ pass -+ -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_below_half_of_the_threshold_not_starting_after_shutdown(topo, setup, reset_logs): -- """Test that the instance won't start if we are below 1/2 of the threshold -+ """Test that the instance won't start if we are below 1/2 of the threshold. - - :id: cceeaefd-9fa4-45c5-9ac6-9887a0671ef8 - :customerscenario: True - :setup: Standalone - :steps: -- 1. Go straight below 1/2 of the threshold -- 2. Try to start the instance -- 3. Go back above the threshold -- 4. Try to start the instance -+ 1. Go below half threshold and wait for shutdown -+ 2. Try to start the instance and verify it fails -+ 3. Free space and verify instance starts successfully - :expectedresults: -- 1. Should Success -- 2. Should Fail -- 3. Should Success -- 4. Should Success -+ 1. Success -+ 2. Startup fails as expected -+ 3. Success - """ -- file_path = '{}/foo'.format(topo.standalone.ds_paths.log_dir) -- topo.standalone.deleteErrorLogs() -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- topo.standalone.restart() -+ log.info("Starting test_below_half_of_the_threshold_not_starting_after_shutdown") -+ inst = topo.standalone -+ fill_file = None -+ - try: -- if float(THRESHOLD) > FULL_THR_FILL_SIZE: -- FULL_THR_FILL_SIZE_new = FULL_THR_FILL_SIZE + round(float(THRESHOLD) - FULL_THR_FILL_SIZE) + 1 -- subprocess.call(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', f'count={FULL_THR_FILL_SIZE_new}']) -- else: -- subprocess.call(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', f'count={FULL_THR_FILL_SIZE}']) -- _withouterrorlog(topo, 'topo.standalone.status() == True', 120) -+ log.info("Configuring disk monitoring") -+ inst.deleteErrorLogs() -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.restart() -+ -+ log.info(f"Filling disk to go below half threshold ({THRESHOLD_BYTES // 2} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ log.info("Waiting for server to shut down due to disk space") -+ wait_for_condition(inst, 'inst.status() == True', 120) -+ -+ log.info("Attempting to start instance (should fail)") - try: -- topo.standalone.start() -+ inst.start() -+ assert False, "Instance startup should have failed due to low disk space" - except (ValueError, subprocess.CalledProcessError): -- topo.standalone.log.info("Instance start up has failed as expected") -- _witherrorlog(topo, f'is too far below the threshold({THRESHOLD_BYTES} bytes). Exiting now', 2) -- # Verify DS has recovered from shutdown -- os.remove(file_path) -- topo.standalone.start() -+ log.info("Instance startup failed as expected due to low disk space") -+ -+ wait_for_log_entry(inst, f'is too far below the threshold({THRESHOLD_BYTES} bytes). Exiting now', 2) -+ -+ log.info("Freeing disk space") -+ os.remove(fill_file) -+ fill_file = None -+ -+ log.info("Starting instance after freeing space") -+ inst.start() -+ assert inst.status() == True -+ log.info("Instance started successfully after freeing space") -+ - finally: -- if os.path.exists(file_path): -- os.remove(file_path) -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_go_straight_below_4kb(topo, setup, reset_logs): -- """Go straight below 4KB -+ """Go straight below 4KB and verify behavior. - - :id: a855115a-fe9e-11e8-8e91-8c16451d917b - :setup: Standalone - :steps: - 1. Go straight below 4KB -- 2. Clean space -+ 2. Verify server behavior -+ 3. Clean space and restart - :expectedresults: -- 1. Should Success -- 2. Should Success -+ 1. Success -+ 2. Success -+ 3. Success - """ -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- topo.standalone.restart() -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo1'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(FULL_THR_FILL_SIZE)]) -- _withouterrorlog(topo, 'topo.standalone.status() != False', 11) -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -- os.remove('{}/foo1'.format(topo.standalone.ds_paths.log_dir)) -- topo.standalone.start() -- assert topo.standalone.status() == True -+ log.info("Starting test_go_straight_below_4kb") -+ inst = topo.standalone -+ fill_file = None -+ -+ try: -+ log.info("Configuring disk monitoring") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.restart() -+ -+ log.info("Filling disk to go below 4KB") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, 4000) -+ -+ log.info("Waiting for server shutdown due to extreme low disk space") -+ wait_for_condition(inst, 'inst.status() != False', 11) -+ -+ log.info("Freeing disk space and restarting") -+ os.remove(fill_file) -+ fill_file = None -+ inst.start() -+ -+ assert inst.status() == True -+ log.info("Server restarted successfully after freeing space") -+ -+ finally: -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) -+ -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - @pytest.mark.bz982325 - def test_threshold_to_overflow_value(topo, setup, reset_logs): -- """Overflow in nsslapd-disk-monitoring-threshold -+ """Test overflow in nsslapd-disk-monitoring-threshold. - - :id: ad60ab3c-fe9e-11e8-88dc-8c16451d917b - :setup: Standalone - :steps: -- 1. Setting nsslapd-disk-monitoring-threshold to overflow_value -+ 1. Set nsslapd-disk-monitoring-threshold to overflow value -+ 2. Verify the value is set correctly - :expectedresults: -- 1. Should Success -+ 1. Success -+ 2. Success - """ -+ log.info("Starting test_threshold_to_overflow_value") -+ inst = topo.standalone -+ - overflow_value = '3000000000' -- # Setting nsslapd-disk-monitoring-threshold to overflow_value -- assert topo.standalone.config.set('nsslapd-disk-monitoring-threshold', ensure_bytes(overflow_value)) -- assert overflow_value == re.findall(r'nsslapd-disk-monitoring-threshold: \d+', str( -- topo.standalone.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', -- ['nsslapd-disk-monitoring-threshold'])))[0].split(' ')[1] -+ log.info(f"Setting threshold to overflow value: {overflow_value}") -+ -+ inst.config.set('nsslapd-disk-monitoring-threshold', ensure_bytes(overflow_value)) -+ -+ config_entry = inst.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-disk-monitoring-threshold']) -+ current_value = re.findall(r'nsslapd-disk-monitoring-threshold: \d+', str(config_entry))[0].split(' ')[1] -+ assert overflow_value == current_value -+ -+ log.info(f"Verified: threshold value set to {current_value}") -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - @pytest.mark.bz970995 - def test_threshold_is_reached_to_half(topo, setup, reset_logs): -- """RHDS not shutting down when disk monitoring threshold is reached to half. -+ """Verify RHDS not shutting down when disk monitoring threshold is reached to half. - - :id: b2d3665e-fe9e-11e8-b9c0-8c16451d917b - :setup: Standalone -- :steps: Standalone -- 1. Verify that there is not endless loop of error messages -+ :steps: -+ 1. Configure disk monitoring with critical logging -+ 2. Go below threshold -+ 3. Verify there is no endless loop of error messages - :expectedresults: -- 1. Should Success -+ 1. Success -+ 2. Success -+ 3. Success - """ -+ log.info("Starting test_threshold_is_reached_to_half") -+ inst = topo.standalone -+ fill_file = None -+ -+ try: -+ log.info("Configuring disk monitoring with critical logging enabled") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.config.set('nsslapd-disk-monitoring-threshold', ensure_bytes(str(THRESHOLD_BYTES))) -+ inst.restart() -+ -+ log.info(f"Filling disk to go below threshold ({THRESHOLD_BYTES} bytes)") -+ fill_file = fill_to_target_avail(inst.ds_paths.log_dir, THRESHOLD_BYTES // 2 - 1) -+ -+ log.info("Waiting for loglevel message and verifying it's not repeated") -+ wait_for_log_entry(inst, "temporarily setting error loglevel to the default level", 11) -+ -+ with open(inst.errlog, 'r') as err_log: -+ content = err_log.read() -+ -+ message_count = len(re.findall("temporarily setting error loglevel to the default level", content)) -+ assert message_count == 1, f"Expected 1 occurrence of message, found {message_count}" -+ -+ log.info("Verified: no endless loop of error messages") -+ -+ finally: -+ if fill_file and os.path.exists(fill_file): -+ log.debug(f"Cleaning up fill file: {fill_file}") -+ os.remove(fill_file) - -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-threshold', ensure_bytes(THRESHOLD_BYTES)) -- topo.standalone.restart() -- subprocess.call(['dd', 'if=/dev/zero', 'of={}/foo'.format(topo.standalone.ds_paths.log_dir), 'bs=1M', 'count={}'.format(HALF_THR_FILL_SIZE)]) -- # Verify that there is not endless loop of error messages -- _witherrorlog(topo, "temporarily setting error loglevel to the default level", 10) -- with open(topo.standalone.errlog, 'r') as study:study = study.read() -- assert len(re.findall("temporarily setting error loglevel to the default level", study)) == 1 -- os.remove('{}/foo'.format(topo.standalone.ds_paths.log_dir)) -+ log.info("Test completed successfully") - - - @disk_monitoring_ack -@@ -713,58 +1145,77 @@ def test_threshold_is_reached_to_half(topo, setup, reset_logs): - ("nsslapd-disk-monitoring-grace-period", '0'), - ]) - def test_negagtive_parameterize(topo, setup, reset_logs, test_input, expected): -- """Verify that invalid operations are not permitted -+ """Verify that invalid operations are not permitted. - - :id: b88efbf8-fe9e-11e8-8499-8c16451d917b - :parametrized: yes - :setup: Standalone - :steps: -- 1. Verify that invalid operations are not permitted. -+ 1. Try to set invalid configuration values - :expectedresults: -- 1. Should not success. -+ 1. Configuration change should fail - """ -+ log.info(f"Starting test_negagtive_parameterize for {test_input}={expected}") -+ inst = topo.standalone -+ -+ log.info(f"Attempting to set invalid value: {test_input}={expected}") - with pytest.raises(Exception): -- topo.standalone.config.set(test_input, ensure_bytes(expected)) -+ inst.config.set(test_input, ensure_bytes(expected)) -+ -+ log.info("Verified: invalid configuration value was rejected") -+ log.info("Test completed successfully") - - - @disk_monitoring_ack - def test_valid_operations_are_permitted(topo, setup, reset_logs): -- """Verify that valid operations are permitted -+ """Verify that valid operations are permitted. - - :id: bd4f83f6-fe9e-11e8-88f4-8c16451d917b - :setup: Standalone - :steps: -- 1. Verify that valid operations are permitted -+ 1. Perform various valid configuration operations - :expectedresults: -- 1. Should Success. -+ 1. All operations should succeed - """ -- assert topo.standalone.config.set('nsslapd-disk-monitoring', 'on') -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -- assert topo.standalone.config.set('nsslapd-errorlog-level', '8') -- topo.standalone.restart() -- # Trying to delete nsslapd-disk-monitoring-threshold -- assert topo.standalone.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring-threshold', '')]) -- # Trying to add another value to nsslapd-disk-monitoring-threshold (check that it is not multivalued) -- topo.standalone.config.add('nsslapd-disk-monitoring-threshold', '2000001') -- # Trying to delete nsslapd-disk-monitoring -- assert topo.standalone.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring', ensure_bytes(str( -- topo.standalone.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-disk-monitoring'])[ -- 0]).split(' ')[2].split('\n\n')[0]))]) -- # Trying to add another value to nsslapd-disk-monitoring -- topo.standalone.config.add('nsslapd-disk-monitoring', 'off') -- # Trying to delete nsslapd-disk-monitoring-grace-period -- assert topo.standalone.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring-grace-period', '')]) -- # Trying to add another value to nsslapd-disk-monitoring-grace-period -- topo.standalone.config.add('nsslapd-disk-monitoring-grace-period', '61') -- # Trying to delete nsslapd-disk-monitoring-logging-critical -- assert topo.standalone.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring-logging-critical', -- ensure_bytes(str( -- topo.standalone.search_s('cn=config', ldap.SCOPE_SUBTREE, -- '(objectclass=*)', [ -- 'nsslapd-disk-monitoring-logging-critical'])[ -- 0]).split(' ')[2].split('\n\n')[0]))]) -- # Trying to add another value to nsslapd-disk-monitoring-logging-critical -- assert topo.standalone.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -+ log.info("Starting test_valid_operations_are_permitted") -+ inst = topo.standalone -+ -+ log.info("Setting initial disk monitoring configuration") -+ inst.config.set('nsslapd-disk-monitoring', 'on') -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -+ inst.config.set('nsslapd-errorlog-level', '8') -+ inst.restart() -+ -+ log.info("Testing deletion of nsslapd-disk-monitoring-threshold") -+ inst.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring-threshold', '')]) -+ -+ log.info("Testing addition of nsslapd-disk-monitoring-threshold value") -+ inst.config.add('nsslapd-disk-monitoring-threshold', '2000001') -+ -+ log.info("Testing deletion of nsslapd-disk-monitoring") -+ config_entry = inst.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-disk-monitoring']) -+ current_value = str(config_entry[0]).split(' ')[2].split('\n\n')[0] -+ inst.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring', ensure_bytes(current_value))]) -+ -+ log.info("Testing addition of nsslapd-disk-monitoring value") -+ inst.config.add('nsslapd-disk-monitoring', 'off') -+ -+ log.info("Testing deletion of nsslapd-disk-monitoring-grace-period") -+ inst.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring-grace-period', '')]) -+ -+ log.info("Testing addition of nsslapd-disk-monitoring-grace-period value") -+ inst.config.add('nsslapd-disk-monitoring-grace-period', '61') -+ -+ log.info("Testing deletion of nsslapd-disk-monitoring-logging-critical") -+ config_entry = inst.search_s('cn=config', ldap.SCOPE_SUBTREE, '(objectclass=*)', ['nsslapd-disk-monitoring-logging-critical']) -+ current_value = str(config_entry[0]).split(' ')[2].split('\n\n')[0] -+ inst.modify_s('cn=config', [(ldap.MOD_DELETE, 'nsslapd-disk-monitoring-logging-critical', ensure_bytes(current_value))]) -+ -+ log.info("Testing addition of nsslapd-disk-monitoring-logging-critical value") -+ inst.config.set('nsslapd-disk-monitoring-logging-critical', 'on') -+ -+ log.info("All valid operations completed successfully") -+ log.info("Test completed successfully") - - - if __name__ == '__main__': --- -2.49.0 - diff --git a/0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch b/0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch deleted file mode 100644 index fd7bf88..0000000 --- a/0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 574a5295e13cf01c34226d676104057468198616 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Fri, 4 Oct 2024 08:55:11 -0700 -Subject: [PATCH] Issue 6339 - Address Coverity scan issues in memberof and - bdb_layer (#6353) - -Description: Add null check for memberof attribute in memberof.c -Fix memory leak by freeing 'cookie' in memberof.c -Add null check for database environment in bdb_layer.c -Fix race condition by adding mutex lock/unlock in bdb_layer.c - -Fixes: https://github.com/389ds/389-ds-base/issues/6339 - -Reviewed by: @progier389, @tbordaz (Thanks!) ---- - ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c | 17 ++++++++++++++--- - 1 file changed, 14 insertions(+), 3 deletions(-) - -diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c -index b04cd68e2..4f069197e 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c -@@ -6987,6 +6987,7 @@ bdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_t - bdb_config *conf = (bdb_config *)li->li_dblayer_config; - bdb_db_env **ppEnv = (bdb_db_env**)&priv->dblayer_env; - char dbhome[MAXPATHLEN]; -+ bdb_db_env *pEnv = NULL; - DB_ENV *bdb_env = NULL; - DB *bdb_db = NULL; - struct stat st = {0}; -@@ -7036,7 +7037,13 @@ bdb_public_private_open(backend *be, const char *db_filename, int rw, dbi_env_t - conf->bdb_tx_max = 50; - rc = bdb_start(li, DBLAYER_NORMAL_MODE); - if (rc == 0) { -- bdb_env = ((struct bdb_db_env*)(priv->dblayer_env))->bdb_DB_ENV; -+ pEnv = (bdb_db_env *)priv->dblayer_env; -+ if (pEnv == NULL) { -+ fprintf(stderr, "bdb_public_private_open: dbenv is not available (0x%p) for database %s\n", -+ (void *)pEnv, db_filename ? db_filename : "unknown"); -+ return EINVAL; -+ } -+ bdb_env = pEnv->bdb_DB_ENV; - } - } else { - /* Setup minimal environment */ -@@ -7080,8 +7087,12 @@ bdb_public_private_close(struct ldbminfo *li, dbi_env_t **env, dbi_db_t **db) - if (priv) { - /* Detect if db is fully set up in read write mode */ - bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env; -- if (pEnv && pEnv->bdb_thread_count>0) { -- rw = 1; -+ if (pEnv) { -+ pthread_mutex_lock(&pEnv->bdb_thread_count_lock); -+ if (pEnv->bdb_thread_count > 0) { -+ rw = 1; -+ } -+ pthread_mutex_unlock(&pEnv->bdb_thread_count_lock); - } - } - if (rw == 0) { --- -2.49.0 - diff --git a/0021-Issue-6468-CLI-Fix-default-error-log-level.patch b/0021-Issue-6468-CLI-Fix-default-error-log-level.patch deleted file mode 100644 index 884e524..0000000 --- a/0021-Issue-6468-CLI-Fix-default-error-log-level.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 972ddeed2029975d5d89e165db1db554f2e8bc28 Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Tue, 29 Jul 2025 08:00:00 +0200 -Subject: [PATCH] Issue 6468 - CLI - Fix default error log level - -Description: -Default error log level is 16384 - -Relates: https://github.com/389ds/389-ds-base/issues/6468 - -Reviewed by: @droideck (Thanks!) ---- - src/lib389/lib389/cli_conf/logging.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/lib389/lib389/cli_conf/logging.py b/src/lib389/lib389/cli_conf/logging.py -index d1e32822c..c48c75faa 100644 ---- a/src/lib389/lib389/cli_conf/logging.py -+++ b/src/lib389/lib389/cli_conf/logging.py -@@ -44,7 +44,7 @@ ERROR_LEVELS = { - + "methods used for a SASL bind" - }, - "default": { -- "level": 6384, -+ "level": 16384, - "desc": "Default logging level" - }, - "filter": { --- -2.49.0 - diff --git a/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch b/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch deleted file mode 100644 index 2f5675e..0000000 --- a/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch +++ /dev/null @@ -1,222 +0,0 @@ -From f28deac93c552a9c4dc9dd9c18f449fcd5cc7731 Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Fri, 1 Aug 2025 09:28:39 -0700 -Subject: [PATCH] Issues 6913, 6886, 6250 - Adjust xfail marks (#6914) - -Description: Some of the ACI invalid syntax issues were fixed, -so we need to remove xfail marks. -Disk space issue should have a 'skipif' mark. -Display all attrs (nsslapd-auditlog-display-attrs: *) fails because of a bug. -EntryUSN inconsistency and overflow bugs were exposed with the tests. - -Related: https://github.com/389ds/389-ds-base/issues/6913 -Related: https://github.com/389ds/389-ds-base/issues/6886 -Related: https://github.com/389ds/389-ds-base/issues/6250 - -Reviewed by: @vashirov (Thanks!) ---- - dirsrvtests/tests/suites/acl/syntax_test.py | 13 ++++++++-- - .../tests/suites/import/regression_test.py | 18 +++++++------- - .../logging/audit_password_masking_test.py | 24 +++++++++---------- - .../suites/plugins/entryusn_overflow_test.py | 2 ++ - 4 files changed, 34 insertions(+), 23 deletions(-) - -diff --git a/dirsrvtests/tests/suites/acl/syntax_test.py b/dirsrvtests/tests/suites/acl/syntax_test.py -index 4edc7fa4b..ed9919ba3 100644 ---- a/dirsrvtests/tests/suites/acl/syntax_test.py -+++ b/dirsrvtests/tests/suites/acl/syntax_test.py -@@ -190,10 +190,9 @@ FAILED = [('test_targattrfilters_18', - f'(all)userdn="ldap:///anyone";)'), ] - - --@pytest.mark.xfail(reason='https://bugzilla.redhat.com/show_bug.cgi?id=1691473') - @pytest.mark.parametrize("real_value", [a[1] for a in FAILED], - ids=[a[0] for a in FAILED]) --def test_aci_invalid_syntax_fail(topo, real_value): -+def test_aci_invalid_syntax_fail(topo, real_value, request): - """Try to set wrong ACI syntax. - - :id: 83c40784-fff5-49c8-9535-7064c9c19e7e -@@ -206,6 +205,16 @@ def test_aci_invalid_syntax_fail(topo, real_value): - 1. It should pass - 2. It should not pass - """ -+ # Mark specific test cases as xfail -+ xfail_cases = [ -+ 'test_targattrfilters_18', -+ 'test_targattrfilters_20', -+ 'test_bind_rule_set_with_more_than_three' -+ ] -+ -+ if request.node.callspec.id in xfail_cases: -+ pytest.xfail("DS6913 - This test case is expected to fail") -+ - domain = Domain(topo.standalone, DEFAULT_SUFFIX) - with pytest.raises(ldap.INVALID_SYNTAX): - domain.add("aci", real_value) -diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py -index 2f850a19a..18611de35 100644 ---- a/dirsrvtests/tests/suites/import/regression_test.py -+++ b/dirsrvtests/tests/suites/import/regression_test.py -@@ -323,7 +323,7 @@ ou: myDups00001 - - @pytest.mark.bz1749595 - @pytest.mark.tier2 --@pytest.mark.xfail(not _check_disk_space(), reason="not enough disk space for lmdb map") -+@pytest.mark.skipif(not _check_disk_space(), reason="not enough disk space for lmdb map") - @pytest.mark.xfail(ds_is_older("1.3.10.1"), reason="bz1749595 not fixed on versions older than 1.3.10.1") - def test_large_ldif2db_ancestorid_index_creation(topo, _set_mdb_map_size): - """Import with ldif2db a large file - check that the ancestorid index creation phase has a correct performance -@@ -399,39 +399,39 @@ def test_large_ldif2db_ancestorid_index_creation(topo, _set_mdb_map_size): - log.info('Starting the server') - topo.standalone.start() - -- # With lmdb there is no more any special phase for ancestorid -+ # With lmdb there is no more any special phase for ancestorid - # because ancestorsid get updated on the fly while processing the - # entryrdn (by up the parents chain to compute the parentid -- # -+ # - # But there is still a numSubordinates generation phase - if get_default_db_lib() == "mdb": - log.info('parse the errors logs to check lines with "Generating numSubordinates complete." are present') - end_numsubordinates = str(topo.standalone.ds_error_log.match(r'.*Generating numSubordinates complete.*'))[1:-1] - assert len(end_numsubordinates) > 0 -- -+ - else: - log.info('parse the errors logs to check lines with "Starting sort of ancestorid" are present') - start_sort_str = str(topo.standalone.ds_error_log.match(r'.*Starting sort of ancestorid non-leaf IDs*'))[1:-1] - assert len(start_sort_str) > 0 -- -+ - log.info('parse the errors logs to check lines with "Finished sort of ancestorid" are present') - end_sort_str = str(topo.standalone.ds_error_log.match(r'.*Finished sort of ancestorid non-leaf IDs*'))[1:-1] - assert len(end_sort_str) > 0 -- -+ - log.info('parse the error logs for the line with "Gathering ancestorid non-leaf IDs"') - start_ancestorid_indexing_op_str = str(topo.standalone.ds_error_log.match(r'.*Gathering ancestorid non-leaf IDs*'))[1:-1] - assert len(start_ancestorid_indexing_op_str) > 0 -- -+ - log.info('parse the error logs for the line with "Created ancestorid index"') - end_ancestorid_indexing_op_str = str(topo.standalone.ds_error_log.match(r'.*Created ancestorid index*'))[1:-1] - assert len(end_ancestorid_indexing_op_str) > 0 -- -+ - log.info('get the ancestorid non-leaf IDs indexing start and end time from the collected strings') - # Collected lines look like : '[15/May/2020:05:30:27.245967313 -0400] - INFO - bdb_get_nonleaf_ids - import userRoot: Gathering ancestorid non-leaf IDs...' - # We are getting the sec.nanosec part of the date, '27.245967313' in the above example - start_time = (start_ancestorid_indexing_op_str.split()[0]).split(':')[3] - end_time = (end_ancestorid_indexing_op_str.split()[0]).split(':')[3] -- -+ - log.info('Calculate the elapsed time for the ancestorid non-leaf IDs index creation') - etime = (Decimal(end_time) - Decimal(start_time)) - # The time for the ancestorid index creation should be less than 10s for an offline import of an ldif file with 100000 entries / 5 entries per node -diff --git a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py -index 3b6a54849..69a36cb5d 100644 ---- a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py -+++ b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py -@@ -117,10 +117,10 @@ def check_password_masked(inst, log_format, expected_password, actual_password): - - @pytest.mark.parametrize("log_format,display_attrs", [ - ("default", None), -- ("default", "*"), -+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("default", "userPassword"), - ("json", None), -- ("json", "*"), -+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("json", "userPassword") - ]) - def test_password_masking_add_operation(topo, log_format, display_attrs): -@@ -173,10 +173,10 @@ def test_password_masking_add_operation(topo, log_format, display_attrs): - - @pytest.mark.parametrize("log_format,display_attrs", [ - ("default", None), -- ("default", "*"), -+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("default", "userPassword"), - ("json", None), -- ("json", "*"), -+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("json", "userPassword") - ]) - def test_password_masking_modify_operation(topo, log_format, display_attrs): -@@ -242,10 +242,10 @@ def test_password_masking_modify_operation(topo, log_format, display_attrs): - - @pytest.mark.parametrize("log_format,display_attrs", [ - ("default", None), -- ("default", "*"), -+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("default", "nsslapd-rootpw"), - ("json", None), -- ("json", "*"), -+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("json", "nsslapd-rootpw") - ]) - def test_password_masking_rootpw_modify_operation(topo, log_format, display_attrs): -@@ -297,10 +297,10 @@ def test_password_masking_rootpw_modify_operation(topo, log_format, display_attr - - @pytest.mark.parametrize("log_format,display_attrs", [ - ("default", None), -- ("default", "*"), -+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("default", "nsmultiplexorcredentials"), - ("json", None), -- ("json", "*"), -+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("json", "nsmultiplexorcredentials") - ]) - def test_password_masking_multiplexor_credentials(topo, log_format, display_attrs): -@@ -368,10 +368,10 @@ def test_password_masking_multiplexor_credentials(topo, log_format, display_attr - - @pytest.mark.parametrize("log_format,display_attrs", [ - ("default", None), -- ("default", "*"), -+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("default", "nsDS5ReplicaCredentials"), - ("json", None), -- ("json", "*"), -+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("json", "nsDS5ReplicaCredentials") - ]) - def test_password_masking_replica_credentials(topo, log_format, display_attrs): -@@ -432,10 +432,10 @@ def test_password_masking_replica_credentials(topo, log_format, display_attrs): - - @pytest.mark.parametrize("log_format,display_attrs", [ - ("default", None), -- ("default", "*"), -+ pytest.param("default", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("default", "nsDS5ReplicaBootstrapCredentials"), - ("json", None), -- ("json", "*"), -+ pytest.param("json", "*", marks=pytest.mark.xfail(reason="DS6886")), - ("json", "nsDS5ReplicaBootstrapCredentials") - ]) - def test_password_masking_bootstrap_credentials(topo, log_format, display_attrs): -diff --git a/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py -index a23d734ca..8c3a537ab 100644 ---- a/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py -+++ b/dirsrvtests/tests/suites/plugins/entryusn_overflow_test.py -@@ -81,6 +81,7 @@ def setup_usn_test(topology_st, request): - return created_users - - -+@pytest.mark.xfail(reason="DS6250") - def test_entryusn_overflow_on_add_existing_entries(topology_st, setup_usn_test): - """Test that reproduces entryUSN overflow when adding existing entries - -@@ -232,6 +233,7 @@ def test_entryusn_overflow_on_add_existing_entries(topology_st, setup_usn_test): - log.info("EntryUSN overflow test completed successfully") - - -+@pytest.mark.xfail(reason="DS6250") - def test_entryusn_consistency_after_failed_adds(topology_st, setup_usn_test): - """Test that entryUSN remains consistent after failed add operations - --- -2.49.0 - diff --git a/0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch b/0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch deleted file mode 100644 index d208d39..0000000 --- a/0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 58a9e1083865e75bba3cf9867a3df109031d7810 Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Mon, 28 Jul 2025 13:18:26 +0200 -Subject: [PATCH] Issue 6181 - RFE - Allow system to manage uid/gid at startup - -Description: -Expand CapabilityBoundingSet to include CAP_FOWNER - -Relates: https://github.com/389ds/389-ds-base/issues/6181 -Relates: https://github.com/389ds/389-ds-base/issues/6906 - -Reviewed by: @progier389 (Thanks!) ---- - wrappers/systemd.template.service.in | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/wrappers/systemd.template.service.in b/wrappers/systemd.template.service.in -index fa05c9f60..6db1f6f8f 100644 ---- a/wrappers/systemd.template.service.in -+++ b/wrappers/systemd.template.service.in -@@ -25,7 +25,7 @@ MemoryAccounting=yes - - # Allow non-root instances to bind to low ports. - AmbientCapabilities=CAP_NET_BIND_SERVICE --CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE CAP_CHOWN -+CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER - - PrivateTmp=on - # https://en.opensuse.org/openSUSE:Security_Features#Systemd_hardening_effort --- -2.49.0 - diff --git a/0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch b/0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch deleted file mode 100644 index d3ec59d..0000000 --- a/0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch +++ /dev/null @@ -1,92 +0,0 @@ -From e03af0aa7e041fc2ca20caf3bcb5810e968043dc Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Tue, 13 May 2025 13:53:05 +0200 -Subject: [PATCH] Issue 6778 - Memory leak in - roles_cache_create_object_from_entry - -Bug Description: -`this_role` has internal allocations (`dn`, `rolescopedn`, etc.) -that are not freed. - -Fix Description: -Use `roles_cache_role_object_free` to free `this_role` and all its -internal structures. - -Fixes: https://github.com/389ds/389-ds-base/issues/6778 - -Reviewed by: @mreynolds389 (Thanks!) ---- - ldap/servers/plugins/roles/roles_cache.c | 15 ++++++++------- - 1 file changed, 8 insertions(+), 7 deletions(-) - -diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c -index bbed11802..60d7182e2 100644 ---- a/ldap/servers/plugins/roles/roles_cache.c -+++ b/ldap/servers/plugins/roles/roles_cache.c -@@ -1098,7 +1098,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - /* We determine the role type by reading the objectclass */ - if (roles_cache_is_role_entry(role_entry) == 0) { - /* Bad type */ -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); - return SLAPI_ROLE_DEFINITION_ERROR; - } - -@@ -1108,7 +1108,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - this_role->type = type; - } else { - /* Bad type */ -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); - return SLAPI_ROLE_DEFINITION_ERROR; - } - -@@ -1166,7 +1166,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - filter_attr_value = (char *)slapi_entry_attr_get_charptr(role_entry, ROLE_FILTER_ATTR_NAME); - if (filter_attr_value == NULL) { - /* Means probably no attribute or no value there */ -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); - return SLAPI_ROLE_ERROR_NO_FILTER_SPECIFIED; - } - -@@ -1205,7 +1205,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - (char *)slapi_sdn_get_ndn(this_role->dn), - ROLE_FILTER_ATTR_NAME, filter_attr_value, - ROLE_FILTER_ATTR_NAME); -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); - slapi_ch_free_string(&filter_attr_value); - return SLAPI_ROLE_ERROR_FILTER_BAD; - } -@@ -1217,7 +1217,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - filter = slapi_str2filter(filter_attr_value); - if (filter == NULL) { - /* An error has occured */ -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); - slapi_ch_free_string(&filter_attr_value); - return SLAPI_ROLE_ERROR_FILTER_BAD; - } -@@ -1228,7 +1228,8 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - (char *)slapi_sdn_get_ndn(this_role->dn), - filter_attr_value, - ROLE_FILTER_ATTR_NAME); -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); -+ slapi_filter_free(filter, 1); - slapi_ch_free_string(&filter_attr_value); - return SLAPI_ROLE_ERROR_FILTER_BAD; - } -@@ -1285,7 +1286,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - if (rc == 0) { - *result = this_role; - } else { -- slapi_ch_free((void **)&this_role); -+ roles_cache_role_object_free((caddr_t)this_role); - } - - slapi_log_err(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, --- -2.49.0 - diff --git a/0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch b/0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch deleted file mode 100644 index 286ed06..0000000 --- a/0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch +++ /dev/null @@ -1,262 +0,0 @@ -From c8c9d8814bd328d9772b6a248aa142b72430cba1 Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Wed, 16 Jul 2025 11:22:30 +0200 -Subject: [PATCH] Issue 6778 - Memory leak in - roles_cache_create_object_from_entry part 2 - -Bug Description: -Everytime a role with scope DN is processed, we leak rolescopeDN. - -Fix Description: -* Initialize all pointer variables to NULL -* Add additional NULL checks -* Free rolescopeDN -* Move test_rewriter_with_invalid_filter before the DB contains 90k entries -* Use task.wait() for import task completion instead of parsing logs, -increase the timeout - -Fixes: https://github.com/389ds/389-ds-base/issues/6778 - -Reviewed by: @progier389 (Thanks!) ---- - dirsrvtests/tests/suites/roles/basic_test.py | 164 +++++++++---------- - ldap/servers/plugins/roles/roles_cache.c | 10 +- - 2 files changed, 82 insertions(+), 92 deletions(-) - -diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py -index d92d6f0c3..ec208bae9 100644 ---- a/dirsrvtests/tests/suites/roles/basic_test.py -+++ b/dirsrvtests/tests/suites/roles/basic_test.py -@@ -510,6 +510,76 @@ def test_vattr_on_managed_role(topo, request): - - request.addfinalizer(fin) - -+def test_rewriter_with_invalid_filter(topo, request): -+ """Test that server does not crash when having -+ invalid filter in filtered role -+ -+ :id: 5013b0b2-0af6-11f0-8684-482ae39447e5 -+ :setup: standalone server -+ :steps: -+ 1. Setup filtered role with good filter -+ 2. Setup nsrole rewriter -+ 3. Restart the server -+ 4. Search for entries -+ 5. Setup filtered role with bad filter -+ 6. Search for entries -+ :expectedresults: -+ 1. Operation should succeed -+ 2. Operation should succeed -+ 3. Operation should succeed -+ 4. Operation should succeed -+ 5. Operation should succeed -+ 6. Operation should succeed -+ """ -+ inst = topo.standalone -+ entries = [] -+ -+ def fin(): -+ inst.start() -+ for entry in entries: -+ entry.delete() -+ request.addfinalizer(fin) -+ -+ # Setup filtered role -+ roles = FilteredRoles(inst, f'ou=people,{DEFAULT_SUFFIX}') -+ filter_ko = '(&((objectClass=top)(objectClass=nsPerson))' -+ filter_ok = '(&(objectClass=top)(objectClass=nsPerson))' -+ role_properties = { -+ 'cn': 'TestFilteredRole', -+ 'nsRoleFilter': filter_ok, -+ 'description': 'Test good filter', -+ } -+ role = roles.create(properties=role_properties) -+ entries.append(role) -+ -+ # Setup nsrole rewriter -+ rewriters = Rewriters(inst) -+ rewriter_properties = { -+ "cn": "nsrole", -+ "nsslapd-libpath": 'libroles-plugin', -+ "nsslapd-filterrewriter": 'role_nsRole_filter_rewriter', -+ } -+ rewriter = rewriters.ensure_state(properties=rewriter_properties) -+ entries.append(rewriter) -+ -+ # Restart thge instance -+ inst.restart() -+ -+ # Search for entries -+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn) -+ -+ # Set bad filter -+ role_properties = { -+ 'cn': 'TestFilteredRole', -+ 'nsRoleFilter': filter_ko, -+ 'description': 'Test bad filter', -+ } -+ role.ensure_state(properties=role_properties) -+ -+ # Search for entries -+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn) -+ -+ - def test_managed_and_filtered_role_rewrite(topo, request): - """Test that filter components containing 'nsrole=xxx' - are reworked if xxx is either a filtered role or a managed -@@ -581,17 +651,11 @@ def test_managed_and_filtered_role_rewrite(topo, request): - PARENT="ou=people,%s" % DEFAULT_SUFFIX - dbgen_users(topo.standalone, 90000, import_ldif, DEFAULT_SUFFIX, entry_name=RDN, generic=True, parent=PARENT) - -- # online import -+ # Online import - import_task = ImportTask(topo.standalone) - import_task.import_suffix_from_ldif(ldiffile=import_ldif, suffix=DEFAULT_SUFFIX) -- # Check for up to 200sec that the completion -- for i in range(1, 20): -- if len(topo.standalone.ds_error_log.match('.*import userRoot: Import complete. Processed 9000.*')) > 0: -- break -- time.sleep(10) -- import_complete = topo.standalone.ds_error_log.match('.*import userRoot: Import complete. Processed 9000.*') -- assert (len(import_complete) == 1) -- -+ import_task.wait(timeout=400) -+ assert import_task.get_exit_code() == 0 - # Restart server - topo.standalone.restart() - -@@ -715,17 +779,11 @@ def test_not_such_entry_role_rewrite(topo, request): - PARENT="ou=people,%s" % DEFAULT_SUFFIX - dbgen_users(topo.standalone, 91000, import_ldif, DEFAULT_SUFFIX, entry_name=RDN, generic=True, parent=PARENT) - -- # online import -+ # Online import - import_task = ImportTask(topo.standalone) - import_task.import_suffix_from_ldif(ldiffile=import_ldif, suffix=DEFAULT_SUFFIX) -- # Check for up to 200sec that the completion -- for i in range(1, 20): -- if len(topo.standalone.ds_error_log.match('.*import userRoot: Import complete. Processed 9100.*')) > 0: -- break -- time.sleep(10) -- import_complete = topo.standalone.ds_error_log.match('.*import userRoot: Import complete. Processed 9100.*') -- assert (len(import_complete) == 1) -- -+ import_task.wait(timeout=400) -+ assert import_task.get_exit_code() == 0 - # Restart server - topo.standalone.restart() - -@@ -769,76 +827,6 @@ def test_not_such_entry_role_rewrite(topo, request): - request.addfinalizer(fin) - - --def test_rewriter_with_invalid_filter(topo, request): -- """Test that server does not crash when having -- invalid filter in filtered role -- -- :id: 5013b0b2-0af6-11f0-8684-482ae39447e5 -- :setup: standalone server -- :steps: -- 1. Setup filtered role with good filter -- 2. Setup nsrole rewriter -- 3. Restart the server -- 4. Search for entries -- 5. Setup filtered role with bad filter -- 6. Search for entries -- :expectedresults: -- 1. Operation should succeed -- 2. Operation should succeed -- 3. Operation should succeed -- 4. Operation should succeed -- 5. Operation should succeed -- 6. Operation should succeed -- """ -- inst = topo.standalone -- entries = [] -- -- def fin(): -- inst.start() -- for entry in entries: -- entry.delete() -- request.addfinalizer(fin) -- -- # Setup filtered role -- roles = FilteredRoles(inst, f'ou=people,{DEFAULT_SUFFIX}') -- filter_ko = '(&((objectClass=top)(objectClass=nsPerson))' -- filter_ok = '(&(objectClass=top)(objectClass=nsPerson))' -- role_properties = { -- 'cn': 'TestFilteredRole', -- 'nsRoleFilter': filter_ok, -- 'description': 'Test good filter', -- } -- role = roles.create(properties=role_properties) -- entries.append(role) -- -- # Setup nsrole rewriter -- rewriters = Rewriters(inst) -- rewriter_properties = { -- "cn": "nsrole", -- "nsslapd-libpath": 'libroles-plugin', -- "nsslapd-filterrewriter": 'role_nsRole_filter_rewriter', -- } -- rewriter = rewriters.ensure_state(properties=rewriter_properties) -- entries.append(rewriter) -- -- # Restart thge instance -- inst.restart() -- -- # Search for entries -- entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn) -- -- # Set bad filter -- role_properties = { -- 'cn': 'TestFilteredRole', -- 'nsRoleFilter': filter_ko, -- 'description': 'Test bad filter', -- } -- role.ensure_state(properties=role_properties) -- -- # Search for entries -- entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn) -- -- - if __name__ == "__main__": - CURRENT_FILE = os.path.realpath(__file__) - pytest.main("-s -v %s" % CURRENT_FILE) -diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c -index 60d7182e2..60f5a919a 100644 ---- a/ldap/servers/plugins/roles/roles_cache.c -+++ b/ldap/servers/plugins/roles/roles_cache.c -@@ -1117,16 +1117,17 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - - rolescopeDN = slapi_entry_attr_get_charptr(role_entry, ROLE_SCOPE_DN); - if (rolescopeDN) { -- Slapi_DN *rolescopeSDN; -- Slapi_DN *top_rolescopeSDN, *top_this_roleSDN; -+ Slapi_DN *rolescopeSDN = NULL; -+ Slapi_DN *top_rolescopeSDN = NULL; -+ Slapi_DN *top_this_roleSDN = NULL; - - /* Before accepting to use this scope, first check if it belongs to the same suffix */ - rolescopeSDN = slapi_sdn_new_dn_byref(rolescopeDN); -- if ((strlen((char *)slapi_sdn_get_ndn(rolescopeSDN)) > 0) && -+ if (rolescopeSDN && (strlen((char *)slapi_sdn_get_ndn(rolescopeSDN)) > 0) && - (slapi_dn_syntax_check(NULL, (char *)slapi_sdn_get_ndn(rolescopeSDN), 1) == 0)) { - top_rolescopeSDN = roles_cache_get_top_suffix(rolescopeSDN); - top_this_roleSDN = roles_cache_get_top_suffix(this_role->dn); -- if (slapi_sdn_compare(top_rolescopeSDN, top_this_roleSDN) == 0) { -+ if (top_rolescopeSDN && top_this_roleSDN && slapi_sdn_compare(top_rolescopeSDN, top_this_roleSDN) == 0) { - /* rolescopeDN belongs to the same suffix as the role, we can use this scope */ - this_role->rolescopedn = rolescopeSDN; - } else { -@@ -1148,6 +1149,7 @@ roles_cache_create_object_from_entry(Slapi_Entry *role_entry, role_object **resu - rolescopeDN); - slapi_sdn_free(&rolescopeSDN); - } -+ slapi_ch_free_string(&rolescopeDN); - } - - /* Depending upon role type, pull out the remaining information we need */ --- -2.49.0 - diff --git a/0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch b/0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch deleted file mode 100644 index b99f974..0000000 --- a/0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch +++ /dev/null @@ -1,65 +0,0 @@ -From f83a1996e3438e471cec086d53fb94be0c8666aa Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Mon, 7 Jul 2025 23:11:17 +0200 -Subject: [PATCH] Issue 6850 - AddressSanitizer: memory leak in mdb_init - -Bug Description: -`dbmdb_componentid` can be allocated multiple times. To avoid a memory -leak, allocate it only once, and free at the cleanup. - -Fixes: https://github.com/389ds/389-ds-base/issues/6850 - -Reviewed by: @mreynolds389, @tbordaz (Tnanks!) ---- - ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c | 4 +++- - ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c | 2 +- - ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c | 5 +++++ - 3 files changed, 9 insertions(+), 2 deletions(-) - -diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c -index 1f7b71442..bebc83b76 100644 ---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c -+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c -@@ -146,7 +146,9 @@ dbmdb_compute_limits(struct ldbminfo *li) - int mdb_init(struct ldbminfo *li, config_info *config_array) - { - dbmdb_ctx_t *conf = (dbmdb_ctx_t *)slapi_ch_calloc(1, sizeof(dbmdb_ctx_t)); -- dbmdb_componentid = generate_componentid(NULL, "db-mdb"); -+ if (dbmdb_componentid == NULL) { -+ dbmdb_componentid = generate_componentid(NULL, "db-mdb"); -+ } - - li->li_dblayer_config = conf; - strncpy(conf->home, li->li_directory, MAXPATHLEN-1); -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 3ecc47170..c6e9f8b01 100644 ---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c -+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c -@@ -19,7 +19,7 @@ - #include - #include - --Slapi_ComponentId *dbmdb_componentid; -+Slapi_ComponentId *dbmdb_componentid = NULL; - - #define BULKOP_MAX_RECORDS 100 /* Max records handled by a single bulk operations */ - -diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c -index 2d07db9b5..ae10ac7cf 100644 ---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c -+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_misc.c -@@ -49,6 +49,11 @@ dbmdb_cleanup(struct ldbminfo *li) - } - slapi_ch_free((void **)&(li->li_dblayer_config)); - -+ if (dbmdb_componentid != NULL) { -+ release_componentid(dbmdb_componentid); -+ dbmdb_componentid = NULL; -+ } -+ - return 0; - } - --- -2.49.0 - diff --git a/0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch b/0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch deleted file mode 100644 index 6908ba1..0000000 --- a/0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch +++ /dev/null @@ -1,58 +0,0 @@ -From e98acc1bfe2194fcdd0e420777eb65a20d55a64b Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Mon, 7 Jul 2025 22:01:09 +0200 -Subject: [PATCH] Issue 6848 - AddressSanitizer: leak in do_search - -Bug Description: -When there's a BER decoding error and the function goes to -`free_and_return`, the `attrs` variable is not being freed because it's -only freed if `!psearch || rc != 0 || err != 0`, but `err` is still 0 at -that point. - -If we reach `free_and_return` from the `ber_scanf` error path, `attrs` -was never set in the pblock with `slapi_pblock_set()`, so the -`slapi_pblock_get()` call will not retrieve the potentially partially -allocated `attrs` from the BER decoding. - -Fixes: https://github.com/389ds/389-ds-base/issues/6848 - -Reviewed by: @tbordaz, @droideck (Thanks!) ---- - ldap/servers/slapd/search.c | 14 ++++++++++++-- - 1 file changed, 12 insertions(+), 2 deletions(-) - -diff --git a/ldap/servers/slapd/search.c b/ldap/servers/slapd/search.c -index e9b2c3670..f9d03c090 100644 ---- a/ldap/servers/slapd/search.c -+++ b/ldap/servers/slapd/search.c -@@ -235,6 +235,7 @@ do_search(Slapi_PBlock *pb) - log_search_access(pb, base, scope, fstr, "decoding error"); - send_ldap_result(pb, LDAP_PROTOCOL_ERROR, NULL, NULL, 0, - NULL); -+ err = 1; /* Make sure we free everything */ - goto free_and_return; - } - -@@ -420,8 +421,17 @@ free_and_return: - if (!psearch || rc != 0 || err != 0) { - slapi_ch_free_string(&fstr); - slapi_filter_free(filter, 1); -- slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &attrs); -- charray_free(attrs); /* passing NULL is fine */ -+ -+ /* Get attrs from pblock if it was set there, otherwise use local attrs */ -+ char **pblock_attrs = NULL; -+ slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &pblock_attrs); -+ if (pblock_attrs != NULL) { -+ charray_free(pblock_attrs); /* Free attrs from pblock */ -+ slapi_pblock_set(pb, SLAPI_SEARCH_ATTRS, NULL); -+ } else if (attrs != NULL) { -+ /* Free attrs that were allocated but never put in pblock */ -+ charray_free(attrs); -+ } - charray_free(gerattrs); /* passing NULL is fine */ - /* - * Fix for defect 526719 / 553356 : Persistent search op failed. --- -2.49.0 - diff --git a/0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch b/0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch deleted file mode 100644 index 99b5e6f..0000000 --- a/0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 120bc2666b682a27ffd6ace5cc238b33fab32c21 Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Fri, 11 Jul 2025 12:32:38 +0200 -Subject: [PATCH] Issue 6865 - AddressSanitizer: leak in - agmt_update_init_status - -Bug Description: -We allocate an array of `LDAPMod *` pointers, but never free it: - -``` -================================================================= -==2748356==ERROR: LeakSanitizer: detected memory leaks - -Direct leak of 24 byte(s) in 1 object(s) allocated from: - #0 0x7f05e8cb4a07 in __interceptor_malloc (/lib64/libasan.so.6+0xb4a07) - #1 0x7f05e85c0138 in slapi_ch_malloc (/usr/lib64/dirsrv/libslapd.so.0+0x1c0138) - #2 0x7f05e109e481 in agmt_update_init_status ldap/servers/plugins/replication/repl5_agmt.c:2583 - #3 0x7f05e10a0aa5 in agmtlist_shutdown ldap/servers/plugins/replication/repl5_agmtlist.c:789 - #4 0x7f05e10ab6bc in multisupplier_stop ldap/servers/plugins/replication/repl5_init.c:844 - #5 0x7f05e10ab6bc in multisupplier_stop ldap/servers/plugins/replication/repl5_init.c:837 - #6 0x7f05e862507d in plugin_call_func ldap/servers/slapd/plugin.c:2001 - #7 0x7f05e8625be1 in plugin_call_one ldap/servers/slapd/plugin.c:1950 - #8 0x7f05e8625be1 in plugin_dependency_closeall ldap/servers/slapd/plugin.c:1844 - #9 0x55e1a7ff9815 in slapd_daemon ldap/servers/slapd/daemon.c:1275 - #10 0x55e1a7fd36ef in main (/usr/sbin/ns-slapd+0x3e6ef) - #11 0x7f05e80295cf in __libc_start_call_main (/lib64/libc.so.6+0x295cf) - #12 0x7f05e802967f in __libc_start_main_alias_2 (/lib64/libc.so.6+0x2967f) - #13 0x55e1a7fd74a4 in _start (/usr/sbin/ns-slapd+0x424a4) - -SUMMARY: AddressSanitizer: 24 byte(s) leaked in 1 allocation(s). -``` - -Fix Description: -Ensure `mods` is freed in the cleanup code. - -Fixes: https://github.com/389ds/389-ds-base/issues/6865 -Relates: https://github.com/389ds/389-ds-base/issues/6470 - -Reviewed by: @mreynolds389 (Thanks!) ---- - ldap/servers/plugins/replication/repl5_agmt.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c -index 6ffb074d4..c6cfcda07 100644 ---- a/ldap/servers/plugins/replication/repl5_agmt.c -+++ b/ldap/servers/plugins/replication/repl5_agmt.c -@@ -2653,6 +2653,7 @@ agmt_update_init_status(Repl_Agmt *ra) - } else { - PR_Unlock(ra->lock); - } -+ slapi_ch_free((void **)&mods); - slapi_mod_done(&smod_start_time); - slapi_mod_done(&smod_end_time); - slapi_mod_done(&smod_status); --- -2.49.0 - diff --git a/0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch b/0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch deleted file mode 100644 index e82a4f9..0000000 --- a/0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch +++ /dev/null @@ -1,97 +0,0 @@ -From 5cc13c70dfe22d95686bec9214c53f1b4114cd90 Mon Sep 17 00:00:00 2001 -From: James Chapman -Date: Fri, 1 Aug 2025 13:27:02 +0100 -Subject: [PATCH] Issue 6768 - ns-slapd crashes when a referral is added - (#6780) - -Bug description: When a paged result search is successfully run on a referred -suffix, we retrieve the search result set from the pblock and try to release -it. In this case the search result set is NULL, which triggers a SEGV during -the release. - -Fix description: If the search result code is LDAP_REFERRAL, skip deletion of -the search result set. Added test case. - -Fixes: https://github.com/389ds/389-ds-base/issues/6768 - -Reviewed by: @tbordaz, @progier389 (Thank you) ---- - .../paged_results/paged_results_test.py | 46 +++++++++++++++++++ - ldap/servers/slapd/opshared.c | 4 +- - 2 files changed, 49 insertions(+), 1 deletion(-) - -diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py -index fca48db0f..1bb94b53a 100644 ---- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py -+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py -@@ -1271,6 +1271,52 @@ def test_search_stress_abandon(create_40k_users, create_user): - paged_search(conn, create_40k_users.suffix, [req_ctrl], search_flt, searchreq_attrlist, abandon_rate=abandon_rate) - - -+def test_search_referral(topology_st): -+ """Test a paged search on a referred suffix doesnt crash the server. -+ -+ :id: c788bdbf-965b-4f12-ac24-d4d695e2cce2 -+ -+ :setup: Standalone instance -+ -+ :steps: -+ 1. Configure a default referral. -+ 2. Create a paged result search control. -+ 3. Paged result search on referral suffix (doesnt exist on the instance, triggering a referral). -+ 4. Check the server is still running. -+ 5. Remove referral. -+ -+ :expectedresults: -+ 1. Referral sucessfully set. -+ 2. Control created. -+ 3. Search returns ldap.REFERRAL (10). -+ 4. Server still running. -+ 5. Referral removed. -+ """ -+ -+ page_size = 5 -+ SEARCH_SUFFIX = "dc=referme,dc=com" -+ REFERRAL = "ldap://localhost.localdomain:389/o%3dnetscaperoot" -+ -+ log.info('Configuring referral') -+ topology_st.standalone.config.set('nsslapd-referral', REFERRAL) -+ referral = topology_st.standalone.config.get_attr_val_utf8('nsslapd-referral') -+ assert (referral == REFERRAL) -+ -+ log.info('Create paged result search control') -+ req_ctrl = SimplePagedResultsControl(True, size=page_size, cookie='') -+ -+ log.info('Perform a paged result search on referred suffix, no chase') -+ with pytest.raises(ldap.REFERRAL): -+ topology_st.standalone.search_ext_s(SEARCH_SUFFIX, ldap.SCOPE_SUBTREE, serverctrls=[req_ctrl]) -+ -+ log.info('Confirm instance is still running') -+ assert (topology_st.standalone.status()) -+ -+ log.info('Remove referral') -+ topology_st.standalone.config.remove_all('nsslapd-referral') -+ referral = topology_st.standalone.config.get_attr_val_utf8('nsslapd-referral') -+ assert (referral == None) -+ - if __name__ == '__main__': - # Run isolated - # -s for DEBUG mode -diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c -index 14a7dcdfb..03ed60981 100644 ---- a/ldap/servers/slapd/opshared.c -+++ b/ldap/servers/slapd/opshared.c -@@ -879,7 +879,9 @@ op_shared_search(Slapi_PBlock *pb, int send_result) - /* Free the results if not "no_such_object" */ - void *sr = NULL; - slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr); -- be->be_search_results_release(&sr); -+ if (be->be_search_results_release != NULL) { -+ be->be_search_results_release(&sr); -+ } - } - pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx); - rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1); --- -2.49.0 - diff --git a/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch b/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch deleted file mode 100644 index 154475e..0000000 --- a/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch +++ /dev/null @@ -1,268 +0,0 @@ -From def739668dd2728825f1108911abc065f981010c Mon Sep 17 00:00:00 2001 -From: Simon Pichugin -Date: Tue, 19 Aug 2025 16:10:09 -0700 -Subject: [PATCH] Issue 6940 - dsconf monitor server fails with ldapi:// due to - absent server ID (#6941) - -Description: The dsconf monitor server command fails when using ldapi:// -protocol because the server ID is not set, preventing PID retrieval from -defaults.inf. This causes the Web console to fail displaying the "Server -Version" field and potentially other CLI/WebUI issues. - -The fix attempts to derive the server ID from the LDAPI socket path when -not explicitly provided. This covers the common case where the socket name -contains the instance name (e.g., slapd-instance.socket). -If that's not possible, it also attempts to derive the server ID from the -nsslapd-instancedir configuration attribute. The derived server ID -is validated against actual system instances to ensure it exists. -Note that socket names can vary and nsslapd-instancedir can be changed. -This is a best-effort approach for the common naming pattern. - -Also fixes the LDAPI socket path extraction which was incorrectly using -offset 9 instead of 8 for ldapi:// URIs. - -The monitor command now handles missing PIDs gracefully, returning zero -values for process-specific stats instead of failing completely. - -Fixes: https://github.com/389ds/389-ds-base/issues/6940 - -Reviewed by: @vashirov, @mreynolds389 (Thanks!!) ---- - src/lib389/lib389/__init__.py | 93 +++++++++++++++++++++++++++--- - src/lib389/lib389/cli_base/dsrc.py | 4 +- - src/lib389/lib389/monitor.py | 50 ++++++++++++---- - 3 files changed, 124 insertions(+), 23 deletions(-) - -diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py -index 65e70c1dd..e6f9273eb 100644 ---- a/src/lib389/lib389/__init__.py -+++ b/src/lib389/lib389/__init__.py -@@ -17,7 +17,7 @@ - - import sys - import os --from urllib.parse import urlparse -+from urllib.parse import urlparse, unquote - import stat - import pwd - import grp -@@ -67,7 +67,8 @@ from lib389.utils import ( - get_default_db_lib, - selinux_present, - selinux_label_port, -- get_user_is_root) -+ get_user_is_root, -+ get_instance_list) - from lib389.paths import Paths - from lib389.nss_ssl import NssSsl - from lib389.tasks import BackupTask, RestoreTask, Task -@@ -304,6 +305,57 @@ class DirSrv(SimpleLDAPObject, object): - self.dbdir = self.ds_paths.db_dir - self.changelogdir = os.path.join(os.path.dirname(self.dbdir), DEFAULT_CHANGELOG_DB) - -+ def _extract_serverid_from_string(self, text): -+ """Extract serverid from a string containing 'slapd-' pattern. -+ Returns the serverid or None if not found or validation fails. -+ Only attempts derivation if serverid is currently None. -+ """ -+ if getattr(self, 'serverid', None) is not None: -+ return None -+ if not text: -+ return None -+ -+ # Use regex to extract serverid from "slapd-" or "slapd-.socket" -+ match = re.search(r'slapd-([A-Za-z0-9._-]+?)(?:\.socket)?(?:$|/)', text) -+ if not match: -+ return None -+ candidate = match.group(1) -+ -+ self.serverid = candidate -+ try: -+ insts = get_instance_list() -+ except Exception: -+ self.serverid = None -+ return None -+ if f'slapd-{candidate}' in insts or candidate in insts: -+ return candidate -+ # restore original and report failure -+ self.serverid = None -+ return None -+ -+ def _derive_serverid_from_ldapi(self): -+ """Attempt to derive serverid from an LDAPI socket path or URI and -+ verify it exists on the system. Returns the serverid or None. -+ """ -+ socket_path = None -+ if hasattr(self, 'ldapi_socket') and self.ldapi_socket: -+ socket_path = unquote(self.ldapi_socket) -+ elif hasattr(self, 'ldapuri') and isinstance(self.ldapuri, str) and self.ldapuri.startswith('ldapi://'): -+ socket_path = unquote(self.ldapuri[len('ldapi://'):]) -+ -+ return self._extract_serverid_from_string(socket_path) -+ -+ def _derive_serverid_from_instancedir(self): -+ """Extract serverid from nsslapd-instancedir path like '/usr/lib64/dirsrv/slapd-'""" -+ try: -+ from lib389.config import Config -+ config = Config(self) -+ instancedir = config.get_attr_val_utf8_l("nsslapd-instancedir") -+ except Exception: -+ return None -+ -+ return self._extract_serverid_from_string(instancedir) -+ - def rebind(self): - """Reconnect to the DS - -@@ -576,6 +628,15 @@ class DirSrv(SimpleLDAPObject, object): - self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off') - self.isLocal = True - self.log.debug("Allocate %s with %s", self.__class__, self.ldapi_socket) -+ elif self.ldapuri is not None and isinstance(self.ldapuri, str) and self.ldapuri.startswith('ldapi://'): -+ # Try to learn serverid from ldapi uri -+ try: -+ self.ldapi_enabled = 'on' -+ self.ldapi_socket = unquote(self.ldapuri[len('ldapi://'):]) -+ self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off') -+ self.isLocal = True -+ except Exception: -+ pass - # Settings from args of server attributes - self.strict_hostname = args.get(SER_STRICT_HOSTNAME_CHECKING, False) - if self.strict_hostname is True: -@@ -596,9 +657,16 @@ class DirSrv(SimpleLDAPObject, object): - - self.log.debug("Allocate %s with %s:%s", self.__class__, self.host, (self.sslport or self.port)) - -- if SER_SERVERID_PROP in args: -- self.ds_paths = Paths(serverid=args[SER_SERVERID_PROP], instance=self, local=self.isLocal) -+ # Try to determine serverid if not provided -+ if SER_SERVERID_PROP in args and args.get(SER_SERVERID_PROP) is not None: - self.serverid = args.get(SER_SERVERID_PROP, None) -+ elif getattr(self, 'serverid', None) is None and self.isLocal: -+ sid = self._derive_serverid_from_ldapi() -+ if sid: -+ self.serverid = sid -+ -+ if getattr(self, 'serverid', None): -+ self.ds_paths = Paths(serverid=self.serverid, instance=self, local=self.isLocal) - else: - self.ds_paths = Paths(instance=self, local=self.isLocal) - -@@ -1032,6 +1100,17 @@ class DirSrv(SimpleLDAPObject, object): - self.__initPart2() - self.state = DIRSRV_STATE_ONLINE - # Now that we're online, some of our methods may try to query the version online. -+ -+ # After transitioning online, attempt to derive serverid if still unknown. -+ # If we find it, refresh ds_paths and rerun __initPart2 -+ if getattr(self, 'serverid', None) is None and self.isLocal: -+ sid = self._derive_serverid_from_instancedir() -+ if sid: -+ self.serverid = sid -+ # Reinitialize paths with the new serverid -+ self.ds_paths = Paths(serverid=self.serverid, instance=self, local=self.isLocal) -+ if not connOnly: -+ self.__initPart2() - self.__add_brookers__() - - def close(self): -@@ -3569,8 +3648,4 @@ class DirSrv(SimpleLDAPObject, object): - """ - Get the pid of the running server - """ -- pid = pid_from_file(self.pid_file()) -- if pid == 0 or pid is None: -- return 0 -- else: -- return pid -+ return pid_from_file(self.pid_file()) -diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py -index 84567b990..498228ce0 100644 ---- a/src/lib389/lib389/cli_base/dsrc.py -+++ b/src/lib389/lib389/cli_base/dsrc.py -@@ -56,7 +56,7 @@ def dsrc_arg_concat(args, dsrc_inst): - new_dsrc_inst['args'][SER_ROOT_DN] = new_dsrc_inst['binddn'] - if new_dsrc_inst['uri'][0:8] == 'ldapi://': - new_dsrc_inst['args'][SER_LDAPI_ENABLED] = "on" -- new_dsrc_inst['args'][SER_LDAPI_SOCKET] = new_dsrc_inst['uri'][9:] -+ new_dsrc_inst['args'][SER_LDAPI_SOCKET] = new_dsrc_inst['uri'][8:] - new_dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on" - - # Make new -@@ -170,7 +170,7 @@ def dsrc_to_ldap(path, instance_name, log): - dsrc_inst['args'][SER_ROOT_DN] = dsrc_inst['binddn'] - if dsrc_inst['uri'][0:8] == 'ldapi://': - dsrc_inst['args'][SER_LDAPI_ENABLED] = "on" -- dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][9:] -+ dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][8:] - dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on" - - # Return the dict. -diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py -index 27b99a7e3..bf3e1df76 100644 ---- a/src/lib389/lib389/monitor.py -+++ b/src/lib389/lib389/monitor.py -@@ -92,21 +92,47 @@ class Monitor(DSLdapObject): - Get CPU and memory stats - """ - stats = {} -- pid = self._instance.get_pid() -+ try: -+ pid = self._instance.get_pid() -+ except Exception: -+ pid = None - total_mem = psutil.virtual_memory()[0] -- p = psutil.Process(pid) -- memory_stats = p.memory_full_info() - -- # Get memory & CPU stats -+ # Always include total system memory - stats['total_mem'] = [str(total_mem)] -- stats['rss'] = [str(memory_stats[0])] -- stats['vms'] = [str(memory_stats[1])] -- stats['swap'] = [str(memory_stats[9])] -- stats['mem_rss_percent'] = [str(round(p.memory_percent("rss")))] -- stats['mem_vms_percent'] = [str(round(p.memory_percent("vms")))] -- stats['mem_swap_percent'] = [str(round(p.memory_percent("swap")))] -- stats['total_threads'] = [str(p.num_threads())] -- stats['cpu_usage'] = [str(round(p.cpu_percent(interval=0.1)))] -+ -+ # Process-specific stats - only if process is running (pid is not None) -+ if pid is not None: -+ try: -+ p = psutil.Process(pid) -+ memory_stats = p.memory_full_info() -+ -+ # Get memory & CPU stats -+ stats['rss'] = [str(memory_stats[0])] -+ stats['vms'] = [str(memory_stats[1])] -+ stats['swap'] = [str(memory_stats[9])] -+ stats['mem_rss_percent'] = [str(round(p.memory_percent("rss")))] -+ stats['mem_vms_percent'] = [str(round(p.memory_percent("vms")))] -+ stats['mem_swap_percent'] = [str(round(p.memory_percent("swap")))] -+ stats['total_threads'] = [str(p.num_threads())] -+ stats['cpu_usage'] = [str(round(p.cpu_percent(interval=0.1)))] -+ except (psutil.NoSuchProcess, psutil.AccessDenied): -+ # Process exists in PID file but is not accessible or doesn't exist -+ pid = None -+ -+ # If no valid PID, provide zero values for process stats -+ if pid is None: -+ stats['rss'] = ['0'] -+ stats['vms'] = ['0'] -+ stats['swap'] = ['0'] -+ stats['mem_rss_percent'] = ['0'] -+ stats['mem_vms_percent'] = ['0'] -+ stats['mem_swap_percent'] = ['0'] -+ stats['total_threads'] = ['0'] -+ stats['cpu_usage'] = ['0'] -+ stats['server_status'] = ['PID unavailable'] -+ else: -+ stats['server_status'] = ['Server running'] - - # Connections to DS - if self._instance.port == "0": --- -2.49.0 - diff --git a/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch b/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch deleted file mode 100644 index 4c84c24..0000000 --- a/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch +++ /dev/null @@ -1,1448 +0,0 @@ -From a3bb421391a65699a91343b35e7c0cf7f07a9add Mon Sep 17 00:00:00 2001 -From: progier389 -Date: Thu, 21 Aug 2025 17:30:00 +0200 -Subject: [PATCH] =?UTF-8?q?Issue=206919=20-=20numSubordinates/tombstoneNum?= - =?UTF-8?q?Subordinates=20are=20inconsisten=E2=80=A6=20(#6920)?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -* Issue 6919 - numSubordinates/tombstoneNumSubordinates are inconsistent after import - -Problem was that the number of tombstone was not propperly computed. -With bdb: tombstoneNumSubordinates was not computed: -With lmdb: numSubordinates was also including the tombstones -Fixed the numSubordinates/tombstoneNumSubordinates computation during import by: -walking the entryrdn C keys (because parentid does not contains tombstone on bdb) -checking if the children entry is a tombstone by looking in objectclass index -and increasing numSubordinates/tombstoneNumSubordinates subcount accordingly -performed some code cleanup. -- removed the job->mother containing the hashtable of non leaf entry ids -- moved the function that replace the numSubordinates/tombstoneNumSubordinates -attribute in an entry back in export.c (rather than duplicating it in -db_import.c and db_import.c) -- changed a PR_ASSERT that is no more true. - -Notes: -Not using the parentid index because it does not contains the tombstone on bdb -(although it does on lmdb) -The new subcount computation algorythm was not possible when the code was origionally written -because it requires entrytrdn index and having alls the keys (i.e: no ALLIDs in the indexes) -That was why hash table of ids and idlist was used. ( I removed that code that code generate a serious -overhead if there is a large number of non leaf entries (typically if users entries have children) - -Issue: #6919 - -Reviewed by: @mreynolds389 (Thanks!) - -(cherry picked from commit ea44136753b011be3ee7517883e7ec273e2416bb) ---- - .../numsubordinates_replication_test.py | 124 ++++- - ldap/servers/plugins/replication/cl5_api.c | 2 +- - .../slapd/back-ldbm/db-bdb/bdb_import.c | 492 ++++++++---------- - .../back-ldbm/db-bdb/bdb_import_threads.c | 13 - - .../slapd/back-ldbm/db-bdb/bdb_layer.h | 3 +- - .../slapd/back-ldbm/db-mdb/mdb_import.c | 264 ++++++---- - .../back-ldbm/db-mdb/mdb_import_threads.c | 1 + - .../slapd/back-ldbm/db-mdb/mdb_instance.c | 2 +- - .../slapd/back-ldbm/db-mdb/mdb_layer.h | 2 - - ldap/servers/slapd/back-ldbm/import.c | 59 +++ - ldap/servers/slapd/back-ldbm/import.h | 2 +- - ldap/servers/slapd/back-ldbm/ldif2ldbm.c | 43 -- - src/lib389/lib389/__init__.py | 2 +- - src/lib389/lib389/_mapped_object.py | 2 +- - 14 files changed, 571 insertions(+), 440 deletions(-) - -diff --git a/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py b/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py -index 9ba10657d..2624b2144 100644 ---- a/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py -+++ b/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py -@@ -9,12 +9,15 @@ - import os - import logging - import pytest -+import re - from lib389._constants import DEFAULT_SUFFIX --from lib389.replica import ReplicationManager - from lib389.idm.organizationalunit import OrganizationalUnits - from lib389.idm.user import UserAccounts --from lib389.topologies import topology_i2 as topo_i2 -- -+from lib389.replica import ReplicationManager -+from lib389.tasks import * -+from lib389.tombstone import Tombstones -+from lib389.topologies import topology_i2 as topo_i2, topology_m2 as topo_m2 -+from lib389.utils import get_default_db_lib - - pytestmark = pytest.mark.tier1 - -@@ -26,6 +29,61 @@ else: - log = logging.getLogger(__name__) - - -+def get_test_name(): -+ full_testname = os.getenv('PYTEST_CURRENT_TEST') -+ res = re.match('.*::([^ ]+) .*', full_testname) -+ assert res -+ return res.group(1) -+ -+ -+@pytest.fixture(scope="function") -+def with_container(topo_m2, request): -+ # Creates a organizational unit container with proper cleanup -+ testname = get_test_name() -+ ou = f'test_container_{testname}' -+ S1 = topo_m2.ms["supplier1"] -+ S2 = topo_m2.ms["supplier2"] -+ repl = ReplicationManager(DEFAULT_SUFFIX) -+ -+ log.info(f"Create container ou={ou},{DEFAULT_SUFFIX} on {S1.serverid}") -+ ous1 = OrganizationalUnits(S1, DEFAULT_SUFFIX) -+ container = ous1.create(properties={ -+ 'ou': ou, -+ 'description': f'Test container for {testname} test' -+ }) -+ -+ def fin(): -+ container.delete(recursive=True) -+ repl.wait_for_replication(S1, S2) -+ -+ if not DEBUGGING: -+ request.addfinalizer(fin) -+ repl.wait_for_replication(S1, S2) -+ -+ return container -+ -+ -+def verify_value_against_entries(container, attr, entries, msg): -+ # Check that container attr value match the number of entries -+ num = container.get_attr_val_int(attr) -+ num_entries = len(entries) -+ dns = [ e.dn for e in entries ] -+ log.debug(f"[{msg}] {attr}: entries: {entries}") -+ log.info(f"[{msg}] container is {container}") -+ log.info(f"[{msg}] {attr}: {num} (Expecting: {num_entries})") -+ assert num == num_entries, ( -+ f"{attr} attribute has wrong value: {num} {msg}, was expecting: {num_entries}", -+ f"entries are {dns}" ) -+ -+ -+def verify_subordinates(inst, container, msg): -+ log.info(f"Verify numSubordinates and tombstoneNumSubordinates {msg}") -+ tombstones = Tombstones(inst, container.dn).list() -+ entries = container.search(scope='one') -+ verify_value_against_entries(container, 'numSubordinates', entries, msg) -+ verify_value_against_entries(container, 'tombstoneNumSubordinates', tombstones, msg) -+ -+ - def test_numsubordinates_tombstone_replication_mismatch(topo_i2): - """Test that numSubordinates values match between replicas after tombstone creation - -@@ -136,9 +194,67 @@ def test_numsubordinates_tombstone_replication_mismatch(topo_i2): - f"instance2 has {tombstone_numsubordinates_instance2}. " - ) - -+def test_numsubordinates_tombstone_after_import(topo_m2, with_container): -+ """Test that numSubordinates values are the expected one after an import -+ -+ :id: 67bec454-6bb3-11f0-b9ae-c85309d5c3e3 -+ :setup: Two suppliers instances with an ou container -+ :steps: -+ 1. Create a container (organizational unit) on the first instance -+ 2. Create a user object in that container -+ 3. Delete the user object (this creates a tombstone) -+ 4. Set up replication between the two instances -+ 5. Wait for replication to complete -+ 6. Check numSubordinates on both instances -+ 7. Check tombstoneNumSubordinates on both instances -+ 8. Verify that numSubordinates values match on both instances -+ :expectedresults: -+ 1. Container should be created successfully -+ 2. User object should be created successfully -+ 3. User object should be deleted successfully -+ 4. Replication should be set up successfully -+ 5. Replication should complete successfully -+ 6. numSubordinates should be accessible on both instances -+ 7. tombstoneNumSubordinates should be accessible on both instances -+ 8. numSubordinates values should match on both instances -+ """ -+ -+ S1 = topo_m2.ms["supplier1"] -+ S2 = topo_m2.ms["supplier2"] -+ container = with_container -+ repl = ReplicationManager(DEFAULT_SUFFIX) -+ tasks = Tasks(S1) -+ -+ log.info("Create some user objects in that container") -+ users1 = UserAccounts(S1, DEFAULT_SUFFIX, rdn=f"ou={container.rdn}") -+ users = {} -+ for uid in range(1001,1010): -+ users[uid] = users1.create_test_user(uid=uid) -+ log.info(f"Created user: {users[uid].dn}") -+ -+ for uid in range(1002,1007,2): -+ users[uid].delete() -+ log.info(f"Removing user: {users[uid].dn}") -+ repl.wait_for_replication(S1, S2) -+ -+ ldif_file = f"{S1.get_ldif_dir()}/export.ldif" -+ log.info(f"Export into {ldif_file}") -+ args = {EXPORT_REPL_INFO: True, -+ TASK_WAIT: True} -+ tasks.exportLDIF(DEFAULT_SUFFIX, None, ldif_file, args) -+ -+ verify_subordinates(S1, container, "before importing") -+ -+ # import the ldif file -+ log.info(f"Import from {ldif_file}") -+ args = {TASK_WAIT: True} -+ tasks.importLDIF(DEFAULT_SUFFIX, None, ldif_file, args) -+ -+ verify_subordinates(S1, container, "after importing") -+ - - if __name__ == '__main__': - # Run isolated - # -s for DEBUG mode - CURRENT_FILE = os.path.realpath(__file__) -- pytest.main("-s %s" % CURRENT_FILE) -\ No newline at end of file -+ pytest.main("-s %s" % CURRENT_FILE) -diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c -index 5dbf38054..d88ce7d98 100644 ---- a/ldap/servers/plugins/replication/cl5_api.c -+++ b/ldap/servers/plugins/replication/cl5_api.c -@@ -3211,7 +3211,7 @@ _cl5EnumConsumerRUV(const ruv_enum_data *element, void *arg) - RUV *ruv; - CSN *csn = NULL; - -- PR_ASSERT(element && element->csn && arg); -+ PR_ASSERT(element && arg); - - ruv = (RUV *)arg; - -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 bc4ae5e2b..39edb7d0e 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c -@@ -30,6 +30,18 @@ static int bdb_ancestorid_create_index(backend *be, ImportJob *job); - static int bdb_ancestorid_default_create_index(backend *be, ImportJob *job); - static int bdb_ancestorid_new_idl_create_index(backend *be, ImportJob *job); - -+/* Helper struct used to compute numsubordinates */ -+ -+typedef struct { -+ backend *be; -+ DB_TXN *txn; -+ const char *attrname; -+ struct attrinfo *ai; -+ dbi_db_t *db; -+ DBC *dbc; -+} subcount_cursor_info_t; -+ -+ - /* Start of definitions for a simple cache using a hash table */ - - typedef struct id2idl -@@ -56,6 +68,69 @@ static int bdb_parentid(backend *be, DB_TXN *txn, ID id, ID *ppid); - static int bdb_check_cache(id2idl_hash *ht); - static IDList *bdb_idl_union_allids(backend *be, struct attrinfo *ai, IDList *a, IDList *b); - -+ -+/********** Code to debug numsubordinates/tombstonenumsubordinates computation **********/ -+ -+#ifdef DEBUG_SUBCOUNT -+#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); \ -+ else if (ret == DB_BUFFER_SMALL) \ -+ hexadump(msg, (key).data, 0, (key).ulen); } -+ -+static void -+debug_subcount(const char *funcname, int line, char *msg, ...) -+{ -+ va_list ap; -+ char buff[1024]; -+ va_start(ap, msg); -+ PR_vsnprintf(buff, (sizeof buff), msg, ap); -+ va_end(ap); -+ 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) -+#endif -+ -+ - /********** routines to manipulate the entry fifo **********/ - - /* this is pretty bogus -- could be a HUGE amount of memory */ -@@ -994,173 +1069,85 @@ out: - - return ret; - } --/* Update subordinate count in a hint list, given the parent's ID */ --int --bdb_import_subcount_mother_init(import_subcount_stuff *mothers, ID parent_id, size_t count) --{ -- PR_ASSERT(NULL == PL_HashTableLookup(mothers->hashtable, (void *)((uintptr_t)parent_id))); -- PL_HashTableAdd(mothers->hashtable, (void *)((uintptr_t)parent_id), (void *)count); -- return 0; --} - --/* Look for a subordinate count in a hint list, given the parent's ID */ --static int --bdb_import_subcount_mothers_lookup(import_subcount_stuff *mothers, -- ID parent_id, -- size_t *count) -+static void -+bdb_close_subcount_cursor(subcount_cursor_info_t *info) - { -- size_t stored_count = 0; -- -- *count = 0; -- /* Lookup hash table for ID */ -- stored_count = (size_t)PL_HashTableLookup(mothers->hashtable, -- (void *)((uintptr_t)parent_id)); -- /* If present, return the count found */ -- if (0 != stored_count) { -- *count = stored_count; -- return 0; -+ if (info->dbc) { -+ int ret = info->dbc->c_close(info->dbc); -+ if (ret) { -+ char errfunc[60]; -+ snprintf(errfunc, (sizeof errfunc), "%s[%s]", __FUNCTION__, info->attrname); -+ ldbm_nasty(errfunc, sourcefile, 73, ret); -+ } -+ info->dbc = NULL; -+ } -+ if (info->db) { -+ dblayer_release_index_file(info->be, info->ai, info->db); -+ info->db = NULL; -+ info->ai = NULL; - } -- return -1; --} -- --/* Update subordinate count in a hint list, given the parent's ID */ --int --bdb_import_subcount_mother_count(import_subcount_stuff *mothers, ID parent_id) --{ -- size_t stored_count = 0; -- -- /* Lookup the hash table for the target ID */ -- stored_count = (size_t)PL_HashTableLookup(mothers->hashtable, -- (void *)((uintptr_t)parent_id)); -- PR_ASSERT(0 != stored_count); -- /* Increment the count */ -- stored_count++; -- PL_HashTableAdd(mothers->hashtable, (void *)((uintptr_t)parent_id), (void *)stored_count); -- return 0; - } - - static int --bdb_import_update_entry_subcount(backend *be, ID parentid, size_t sub_count, int isencrypted) -+bdb_open_subcount_cursor(backend *be, const char *attrname, DB_TXN *txn, subcount_cursor_info_t *info) - { -- ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; -+ char errfunc[60]; -+ DB *db = NULL; - int ret = 0; -- modify_context mc = {0}; -- char value_buffer[22] = {0}; /* enough digits for 2^64 children */ -- struct backentry *e = NULL; -- int isreplace = 0; -- char *numsub_str = numsubordinates; -- -- /* Get hold of the parent */ -- e = id2entry(be, parentid, NULL, &ret); -- if ((NULL == e) || (0 != ret)) { -- ldbm_nasty("bdb_import_update_entry_subcount", sourcefile, 5, ret); -- return (0 == ret) ? -1 : ret; -- } -- /* Lock it (not really required since we're single-threaded here, but -- * let's do it so we can reuse the modify routines) */ -- cache_lock_entry(&inst->inst_cache, e); -- modify_init(&mc, e); -- mc.attr_encrypt = isencrypted; -- sprintf(value_buffer, "%lu", (long unsigned int)sub_count); -- /* If it is a tombstone entry, add tombstonesubordinates instead of -- * numsubordinates. */ -- if (slapi_entry_flag_is_set(e->ep_entry, SLAPI_ENTRY_FLAG_TOMBSTONE)) { -- numsub_str = tombstone_numsubordinates; -- } -- /* attr numsubordinates/tombstonenumsubordinates could already exist in -- * the entry, let's check whether it's already there or not */ -- isreplace = (attrlist_find(e->ep_entry->e_attrs, numsub_str) != NULL); -- { -- int op = isreplace ? LDAP_MOD_REPLACE : LDAP_MOD_ADD; -- Slapi_Mods *smods = slapi_mods_new(); - -- slapi_mods_add(smods, op | LDAP_MOD_BVALUES, numsub_str, -- strlen(value_buffer), value_buffer); -- ret = modify_apply_mods(&mc, smods); /* smods passed in */ -- } -- if (0 == ret || LDAP_TYPE_OR_VALUE_EXISTS == ret) { -- /* This will correctly index subordinatecount: */ -- ret = modify_update_all(be, NULL, &mc, NULL); -- if (0 == ret) { -- modify_switch_entries(&mc, be); -- } -+ snprintf(errfunc, (sizeof errfunc), "%s[%s]", __FUNCTION__, attrname); -+ info->attrname = attrname; -+ info->txn = txn; -+ info->be = be; -+ -+ /* Lets get the attrinfo */ -+ ainfo_get(be, (char*)attrname, &info->ai); -+ PR_ASSERT(info->ai); -+ /* Lets get the db instance */ -+ if ((ret = dblayer_get_index_file(be, info->ai, &info->db, 0)) != 0) { -+ if (ret == DBI_RC_NOTFOUND) { -+ bdb_close_subcount_cursor(info); -+ return 0; -+ } -+ ldbm_nasty(errfunc, sourcefile, 70, ret); -+ bdb_close_subcount_cursor(info); -+ return ret; - } -- /* entry is unlocked and returned to the cache in modify_term */ -- modify_term(&mc, be); -- return ret; --} --struct _import_subcount_trawl_info --{ -- struct _import_subcount_trawl_info *next; -- ID id; -- size_t sub_count; --}; --typedef struct _import_subcount_trawl_info import_subcount_trawl_info; -- --static void --bdb_import_subcount_trawl_add(import_subcount_trawl_info **list, ID id) --{ -- import_subcount_trawl_info *new_info = CALLOC(import_subcount_trawl_info); - -- new_info->next = *list; -- new_info->id = id; -- *list = new_info; -+ /* Lets get the cursor */ -+ db = (DB*)(info->db); -+ if ((ret = db->cursor(db, info->txn, &info->dbc, 0)) != 0) { -+ ldbm_nasty(errfunc, sourcefile, 71, ret); -+ bdb_close_subcount_cursor(info); -+ ret = bdb_map_error(__FUNCTION__, ret); -+ } -+ return 0; - } - --static int --bdb_import_subcount_trawl(backend *be, -- import_subcount_trawl_info *trawl_list, -- int isencrypted) -+static bool -+bdb_subcount_is_tombstone(subcount_cursor_info_t *info, DBT *id) - { -- ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; -- ID id = 1; -- int ret = 0; -- import_subcount_trawl_info *current = NULL; -- char value_buffer[20]; /* enough digits for 2^64 children */ -- -- /* OK, we do */ -- /* We open id2entry and iterate through it */ -- /* Foreach entry, we check to see if its parentID matches any of the -- * values in the trawl list . If so, we bump the sub count for that -- * parent in the list. -+ /* -+ * Check if record =nstombstone ==> id exists in objectclass index - */ -- while (1) { -- struct backentry *e = NULL; -- -- /* Get the next entry */ -- e = id2entry(be, id, NULL, &ret); -- if ((NULL == e) || (0 != ret)) { -- if (DB_NOTFOUND == ret) { -- break; -- } else { -- ldbm_nasty("bdb_import_subcount_trawl", sourcefile, 8, ret); -- return ret; -- } -- } -- for (current = trawl_list; current != NULL; current = current->next) { -- sprintf(value_buffer, "%lu", (u_long)current->id); -- if (slapi_entry_attr_hasvalue(e->ep_entry, LDBM_PARENTID_STR, value_buffer)) { -- /* If this entry's parent ID matches one we're trawling for, -- * bump its count */ -- current->sub_count++; -- } -- } -- /* Free the entry */ -- CACHE_REMOVE(&inst->inst_cache, e); -- CACHE_RETURN(&inst->inst_cache, &e); -- id++; -- } -- /* Now update the parent entries from the list */ -- for (current = trawl_list; current != NULL; current = current->next) { -- /* Update the parent entry with the correctly counted subcount */ -- ret = bdb_import_update_entry_subcount(be, current->id, -- current->sub_count, isencrypted); -- if (0 != ret) { -- ldbm_nasty("bdb_import_subcount_trawl", sourcefile, 10, ret); -- break; -- } -+ DBT key = {0}; -+ DBC *dbc = info->dbc; -+ int ret; -+ key.flags = DB_DBT_USERMEM; -+ key.data = "=nstombstone" ; -+ key.size = key.ulen = 13; -+ ret = dbc->c_get(dbc, &key, id, DB_GET_BOTH); -+ -+ switch (ret) { -+ case 0: -+ return true; -+ case DB_NOTFOUND: -+ return false; -+ default: -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 72, ret); -+ return false; - } -- return ret; - } - - /* -@@ -1172,65 +1159,69 @@ bdb_import_subcount_trawl(backend *be, - static int - bdb_update_subordinatecounts(backend *be, ImportJob *job, DB_TXN *txn) - { -- import_subcount_stuff *mothers = job->mothers; -- int isencrypted = job->encrypt; -+ subcount_cursor_info_t c_objectclass = {0}; -+ subcount_cursor_info_t c_entryrdn = {0}; - int started_progress_logging = 0; -+ int isencrypted = job->encrypt; -+ DBT data = {0}; -+ DBT key = {0}; - int key_count = 0; -+ char tmp[11]; -+ char oldkey[11]; -+ ID data_data; -+ int ret2 = 0; - int ret = 0; -- DB *db = NULL; -- DBC *dbc = NULL; -- struct attrinfo *ai = NULL; -- DBT key = {0}; -- dbi_val_t dbikey = {0}; -- DBT data = {0}; -- import_subcount_trawl_info *trawl_list = NULL; -- -- /* Open the parentid index */ -- ainfo_get(be, LDBM_PARENTID_STR, &ai); - -- /* Open the parentid index file */ -- if ((ret = dblayer_get_index_file(be, ai, (dbi_db_t**)&db, DBOPEN_CREATE)) != 0) { -- ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 67, ret); -- return (ret); -+ /* Open cursor on the objectclass index */ -+ ret = bdb_open_subcount_cursor(be, SLAPI_ATTR_OBJECTCLASS, txn, &c_objectclass); -+ if (ret) { -+ if (ret != DBI_RC_NOTFOUND) { -+ /* No database ==> There is nothing to do. */ -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 61, ret); -+ } -+ return ret; - } -- /* Get a cursor so we can walk through the parentid */ -- ret = db->cursor(db, txn, &dbc, 0); -- if (ret != 0) { -- ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 68, ret); -- dblayer_release_index_file(be, ai, db); -+ /* Open entryrdn index */ -+ /* Open cursor on the entryrdn index */ -+ ret = bdb_open_subcount_cursor(be, LDBM_ENTRYRDN_STR, txn, &c_entryrdn); -+ if (ret) { -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 62, ret); -+ bdb_close_subcount_cursor(&c_objectclass); - return ret; - } - -- /* Walk along the index */ -- while (1) { -+ key.flags = DB_DBT_USERMEM; -+ key.ulen = sizeof tmp; -+ key.data = tmp; -+ /* Only the first 4 bytes of the data record interrest us */ -+ data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; -+ data.ulen = sizeof data_data; -+ data.data = &data_data; -+ data.dlen = sizeof (ID); -+ data.doff = 0; -+ -+ /* Walk along C* keys (usually starting at C1) */ -+ strcpy(tmp, "C"); -+ key.size = 1; -+ ret = c_entryrdn.dbc->c_get(c_entryrdn.dbc, &key, &data, DB_SET_RANGE); -+ -+ while (ret == 0) { - size_t sub_count = 0; -- int found_count = 1; -+ size_t t_sub_count = 0; - ID parentid = 0; - -- /* Foreach key which is an equality key : */ -- data.flags = DB_DBT_MALLOC; -- key.flags = DB_DBT_MALLOC; -- ret = dbc->c_get(dbc, &key, &data, DB_NEXT_NODUP); -- if (NULL != data.data) { -- slapi_ch_free(&(data.data)); -- data.data = NULL; -- } -- if (0 != ret) { -- if (ret != DB_NOTFOUND) { -- ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 62, ret); -- } -- if (NULL != key.data) { -- slapi_ch_free(&(key.data)); -- key.data = NULL; -- } -- break; -- } -+ DUMP_SUBCOUNT_KEY("key:", key, ret); -+ DUMP_SUBCOUNT_KEY("data:", data, ret); - /* check if we need to abort */ - if (job->flags & FLAG_ABORT) { - import_log_notice(job, SLAPI_LOG_ERR, "bdb_update_subordinatecounts", - "numsubordinate generation aborted."); - break; - } -+ if (0 != ret) { -+ ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 63, ret); -+ break; -+ } - /* - * Do an update count - */ -@@ -1241,57 +1232,47 @@ bdb_update_subordinatecounts(backend *be, ImportJob *job, DB_TXN *txn) - key_count); - started_progress_logging = 1; - } -+ if (key.size == 0 || *(char *)key.data != 'C') { -+ /* No more children */ -+ break; -+ } - -- if (*(char *)key.data == EQ_PREFIX) { -- char *idptr = NULL; -- -- /* construct the parent's ID from the key */ -- /* Look for the ID in the hint list supplied by the caller */ -- /* If its there, we know the answer already */ -- idptr = (((char *)key.data) + 1); -- parentid = (ID)atol(idptr); -- PR_ASSERT(0 != parentid); -- ret = bdb_import_subcount_mothers_lookup(mothers, parentid, &sub_count); -- if (0 != ret) { -- IDList *idl = NULL; -- -- /* If it's not, we need to compute it ourselves: */ -- /* Load the IDL matching the key */ -- key.flags = DB_DBT_REALLOC; -- ret = NEW_IDL_NO_ALLID; -- bdb_dbt2dbival(&key, &dbikey, PR_FALSE); -- idl = idl_fetch(be, db, &dbikey, NULL, NULL, &ret); -- bdb_dbival2dbt(&dbikey, &key, PR_TRUE); -- dblayer_value_protect_data(be, &dbikey); -- if ((NULL == idl) || (0 != ret)) { -- ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 4, ret); -- dblayer_release_index_file(be, ai, db); -- return (0 == ret) ? -1 : ret; -- } -- /* The number of IDs in the IDL tells us the number of -- * subordinates for the entry */ -- /* Except, the number might be above the allidsthreshold, -- * in which case */ -- if (ALLIDS(idl)) { -- /* We add this ID to the list for which to trawl */ -- bdb_import_subcount_trawl_add(&trawl_list, parentid); -- found_count = 0; -- } else { -- /* We get the count from the IDL */ -- sub_count = idl->b_nids; -- } -- idl_free(&idl); -+ /* construct the parent's ID from the key */ -+ if (key.size >= sizeof tmp) { -+ ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 64, ret); -+ break; -+ } -+ /* Generate expected value for parentid */ -+ tmp[key.size] = 0; -+ parentid = (ID)atol(tmp+1); -+ PR_ASSERT(0 != parentid); -+ strcpy(oldkey,tmp); -+ /* Walk the entries having same key and check if they are tombstone */ -+ do { -+ /* Reorder data_data */ -+ ID old_data_data = data_data; -+ id_internal_to_stored(old_data_data, (char*)&data_data); -+ if (!bdb_subcount_is_tombstone(&c_objectclass, &data)) { -+ sub_count++; -+ } else { -+ t_sub_count++; - } -- /* Did we get the count ? */ -- if (found_count) { -- PR_ASSERT(0 != sub_count); -- /* If so, update the parent now */ -- bdb_import_update_entry_subcount(be, parentid, sub_count, isencrypted); -+ DUMP_SUBCOUNT_KEY("key:", key, ret); -+ DUMP_SUBCOUNT_KEY("data:", data, ret); -+ ret = c_entryrdn.dbc->c_get(c_entryrdn.dbc, &key, &data, DB_NEXT); -+ DUMP_SUBCOUNT_KEY("key:", key, ret); -+ DUMP_SUBCOUNT_KEY("data:", data, ret); -+ if (ret == 0 && key.size < sizeof tmp) { -+ tmp[key.size] = 0; -+ } else { -+ break; - } -- } -- if (NULL != key.data) { -- slapi_ch_free(&(key.data)); -- key.data = NULL; -+ } while (strcmp(key.data, oldkey) == 0); -+ ret2 = import_update_entry_subcount(be, parentid, sub_count, t_sub_count, isencrypted, (dbi_txn_t*)txn); -+ if (ret2) { -+ ret = ret2; -+ ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 65, ret); -+ break; - } - } - if (started_progress_logging) { -@@ -1301,22 +1282,15 @@ bdb_update_subordinatecounts(backend *be, ImportJob *job, DB_TXN *txn) - key_count); - job->numsubordinates = key_count; - } -- -- ret = dbc->c_close(dbc); -- if (0 != ret) { -- ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 6, ret); -- } -- dblayer_release_index_file(be, ai, db); -- -- /* Now see if we need to go trawling through id2entry for the info -- * we need */ -- if (NULL != trawl_list) { -- ret = bdb_import_subcount_trawl(be, trawl_list, isencrypted); -- if (0 != ret) { -- ldbm_nasty("bdb_update_subordinatecounts", sourcefile, 7, ret); -- } -+ if (ret == DB_NOTFOUND || ret == DB_BUFFER_SMALL) { -+ /* No more records or record is the suffix dn -+ * ==> there is no more children to look at -+ */ -+ ret = 0; - } -- return (ret); -+ bdb_close_subcount_cursor(&c_entryrdn); -+ bdb_close_subcount_cursor(&c_objectclass); -+ return ret; - } - - /* Function used to gather a list of indexed attrs */ -@@ -1451,10 +1425,6 @@ bdb_import_free_job(ImportJob *job) - slapi_ch_free((void **)&asabird); - } - job->index_list = NULL; -- if (NULL != job->mothers) { -- import_subcount_stuff_term(job->mothers); -- slapi_ch_free((void **)&job->mothers); -- } - - bdb_back_free_incl_excl(job->include_subtrees, job->exclude_subtrees); - charray_free(job->input_filenames); -@@ -2720,7 +2690,6 @@ bdb_back_ldif2db(Slapi_PBlock *pb) - } - job->starting_ID = 1; - job->first_ID = 1; -- job->mothers = CALLOC(import_subcount_stuff); - - /* how much space should we allocate to index buffering? */ - job->job_index_buffer_size = bdb_import_get_index_buffer_size(); -@@ -2731,7 +2700,6 @@ bdb_back_ldif2db(Slapi_PBlock *pb) - (job->inst->inst_li->li_import_cachesize / 10) + (1024 * 1024); - PR_Unlock(job->inst->inst_li->li_config_mutex); - } -- import_subcount_stuff_init(job->mothers); - - if (job->task != NULL) { - /* count files, use that to track "progress" in cn=tasks */ -diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c -index 762267dd9..12dfb419b 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c -@@ -2275,17 +2275,6 @@ bdb_foreman_do_parentid(ImportJob *job, FifoItem *fi, struct attrinfo *parentid_ - ret = index_addordel_values_ext_sv(be, LDBM_PARENTID_STR, svals, NULL, - entry->ep_id, BE_INDEX_ADD, - NULL, &idl_disposition, NULL); -- if (idl_disposition != IDL_INSERT_NORMAL) { -- char *attr_value = slapi_value_get_berval(svals[0])->bv_val; -- ID parent_id = atol(attr_value); -- -- if (idl_disposition == IDL_INSERT_NOW_ALLIDS) { -- bdb_import_subcount_mother_init(job->mothers, parent_id, -- idl_get_allidslimit(parentid_ai, 0) + 1); -- } else if (idl_disposition == IDL_INSERT_ALLIDS) { -- bdb_import_subcount_mother_count(job->mothers, parent_id); -- } -- } - if (ret != 0) { - import_log_notice(job, SLAPI_LOG_ERR, "bdb_foreman_do_parentid", - "Can't update parentid index (error %d)", ret); -@@ -3158,7 +3147,6 @@ bdb_bulk_import_start(Slapi_PBlock *pb) - job->starting_ID = 1; - job->first_ID = 1; - -- job->mothers = CALLOC(import_subcount_stuff); - /* how much space should we allocate to index buffering? */ - job->job_index_buffer_size = bdb_import_get_index_buffer_size(); - if (job->job_index_buffer_size == 0) { -@@ -3166,7 +3154,6 @@ bdb_bulk_import_start(Slapi_PBlock *pb) - job->job_index_buffer_size = (job->inst->inst_li->li_dbcachesize / 10) + - (1024 * 1024); - } -- import_subcount_stuff_init(job->mothers); - - pthread_mutex_init(&job->wire_lock, NULL); - pthread_cond_init(&job->wire_cv, NULL); -diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h -index 0be6cab49..f66640d2e 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h -@@ -210,8 +210,6 @@ void bdb_restore_file_update(struct ldbminfo *li, const char *directory); - int bdb_import_file_init(ldbm_instance *inst); - void bdb_import_file_update(ldbm_instance *inst); - int bdb_import_file_check(ldbm_instance *inst); --int bdb_import_subcount_mother_init(import_subcount_stuff *mothers, ID parent_id, size_t count); --int bdb_import_subcount_mother_count(import_subcount_stuff *mothers, ID parent_id); - void bdb_import_configure_index_buffer_size(size_t size); - size_t bdb_import_get_index_buffer_size(void); - int bdb_ldbm_back_wire_import(Slapi_PBlock *pb); -@@ -230,6 +228,7 @@ int bdb_public_in_import(ldbm_instance *inst); - int bdb_dblayer_cursor_iterate(dbi_cursor_t *cursor, - int (*action_cb)(dbi_val_t *key, dbi_val_t *data, void *ctx), - const dbi_val_t *startingkey, void *ctx); -+dbi_error_t bdb_map_error(const char *funcname, int err); - - - /* dbimpl helpers */ -diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c -index ce2151174..d0efc1bca 100644 ---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c -+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c -@@ -26,7 +26,16 @@ - - static char *sourcefile = "dbmdb_import.c"; - --static int dbmdb_import_update_entry_subcount(backend *be, ID parentid, size_t sub_count, int isencrypted, back_txn *txn); -+/* Helper struct used to compute numsubordinates */ -+ -+typedef struct { -+ backend *be; -+ dbi_txn_t *txn; -+ const char *attrname; -+ struct attrinfo *ai; -+ dbi_db_t *db; -+ MDB_cursor *dbc; -+} subcount_cursor_info_t; - - /********** routines to manipulate the entry fifo **********/ - -@@ -126,57 +135,74 @@ dbmdb_import_task_abort(Slapi_Task *task) - - /********** helper functions for importing **********/ - -+static void -+dbmdb_close_subcount_cursor(subcount_cursor_info_t *info) -+{ -+ if (info->dbc) { -+ MDB_CURSOR_CLOSE(info->dbc); -+ info->dbc = NULL; -+ } -+ if (info->db) { -+ dblayer_release_index_file(info->be, info->ai, info->db); -+ info->db = NULL; -+ info->ai = NULL; -+ } -+} -+ - static int --dbmdb_import_update_entry_subcount(backend *be, ID parentid, size_t sub_count, int isencrypted, back_txn *txn) -+dbmdb_open_subcount_cursor(backend *be, const char *attrname, dbi_txn_t *txn, subcount_cursor_info_t *info) - { -- ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; -+ char errfunc[60]; - int ret = 0; -- modify_context mc = {0}; -- char value_buffer[22] = {0}; /* enough digits for 2^64 children */ -- struct backentry *e = NULL; -- int isreplace = 0; -- char *numsub_str = numsubordinates; -- -- /* Get hold of the parent */ -- e = id2entry(be, parentid, txn, &ret); -- if ((NULL == e) || (0 != ret)) { -- slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_update_entry_subcount", "failed to read entry with ID %d ret=%d\n", -- parentid, ret); -- ldbm_nasty("dbmdb_import_update_entry_subcount", sourcefile, 5, ret); -- return (0 == ret) ? -1 : ret; -- } -- /* Lock it (not really required since we're single-threaded here, but -- * let's do it so we can reuse the modify routines) */ -- cache_lock_entry(&inst->inst_cache, e); -- modify_init(&mc, e); -- mc.attr_encrypt = isencrypted; -- sprintf(value_buffer, "%lu", (long unsigned int)sub_count); -- /* If it is a tombstone entry, add tombstonesubordinates instead of -- * numsubordinates. */ -- if (slapi_entry_flag_is_set(e->ep_entry, SLAPI_ENTRY_FLAG_TOMBSTONE)) { -- numsub_str = tombstone_numsubordinates; -- } -- /* attr numsubordinates/tombstonenumsubordinates could already exist in -- * the entry, let's check whether it's already there or not */ -- isreplace = (attrlist_find(e->ep_entry->e_attrs, numsub_str) != NULL); -- { -- int op = isreplace ? LDAP_MOD_REPLACE : LDAP_MOD_ADD; -- Slapi_Mods *smods = slapi_mods_new(); - -- slapi_mods_add(smods, op | LDAP_MOD_BVALUES, numsub_str, -- strlen(value_buffer), value_buffer); -- ret = modify_apply_mods(&mc, smods); /* smods passed in */ -- } -- if (0 == ret || LDAP_TYPE_OR_VALUE_EXISTS == ret) { -- /* This will correctly index subordinatecount: */ -- ret = modify_update_all(be, NULL, &mc, txn); -- if (0 == ret) { -- modify_switch_entries(&mc, be); -+ snprintf(errfunc, (sizeof errfunc), "%s[%s]", __FUNCTION__, attrname); -+ info->attrname = attrname; -+ info->txn = txn; -+ info->be = be; -+ -+ /* Lets get the attrinfo */ -+ ainfo_get(be, (char*)attrname, &info->ai); -+ PR_ASSERT(info->ai); -+ /* Lets get the db instance */ -+ if ((ret = dblayer_get_index_file(be, info->ai, &info->db, 0)) != 0) { -+ if (ret == DBI_RC_NOTFOUND) { -+ dbmdb_close_subcount_cursor(info); -+ return 0; - } -+ ldbm_nasty(errfunc, sourcefile, 70, ret); -+ dbmdb_close_subcount_cursor(info); -+ return ret; -+ } -+ -+ /* Lets get the cursor */ -+ if ((ret = MDB_CURSOR_OPEN(TXN(info->txn), DB(info->db), &info->dbc)) != 0) { -+ ldbm_nasty(errfunc, sourcefile, 71, ret); -+ dbmdb_close_subcount_cursor(info); -+ ret = dbmdb_map_error(__FUNCTION__, ret); -+ } -+ return 0; -+} -+ -+static bool -+dbmdb_subcount_is_tombstone(subcount_cursor_info_t *info, MDB_val *id) -+{ -+ /* -+ * Check if record =nstombstone ==> id exists in objectclass index -+ */ -+ MDB_val key = {0}; -+ int ret; -+ key.mv_data = "=nstombstone" ; -+ key.mv_size = 13; -+ ret = MDB_CURSOR_GET(info->dbc, &key, id, MDB_GET_BOTH); -+ switch (ret) { -+ case 0: -+ return true; -+ case MDB_NOTFOUND: -+ return false; -+ default: -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 72, ret); -+ return false; - } -- /* entry is unlocked and returned to the cache in modify_term */ -- modify_term(&mc, be); -- return ret; - } - - /* -@@ -188,47 +214,56 @@ dbmdb_import_update_entry_subcount(backend *be, ID parentid, size_t sub_count, i - static int - dbmdb_update_subordinatecounts(backend *be, ImportJob *job, dbi_txn_t *txn) - { -- int isencrypted = job->encrypt; -+ subcount_cursor_info_t c_objectclass = {0}; -+ subcount_cursor_info_t c_entryrdn = {0}; - int started_progress_logging = 0; -+ int isencrypted = job->encrypt; -+ MDB_val data = {0}; -+ MDB_val key = {0}; -+ back_txn btxn = {0}; - int key_count = 0; -+ char tmp[11]; -+ int ret2 = 0; - int ret = 0; -- dbmdb_dbi_t*db = NULL; -- MDB_cursor *dbc = NULL; -- struct attrinfo *ai = NULL; -- MDB_val key = {0}; -- MDB_val data = {0}; -- dbmdb_cursor_t cursor = {0}; -- struct ldbminfo *li = (struct ldbminfo*)be->be_database->plg_private; -- back_txn btxn = {0}; -- -- /* Open the parentid index */ -- ainfo_get(be, LDBM_PARENTID_STR, &ai); - -- /* Open the parentid index file */ -- if ((ret = dblayer_get_index_file(be, ai, (dbi_db_t**)&db, DBOPEN_CREATE)) != 0) { -- ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 67, ret); -- return (ret); -- } -- /* Get a cursor with r/w txn so we can walk through the parentid */ -- ret = dbmdb_open_cursor(&cursor, MDB_CONFIG(li), db, 0); -- if (ret != 0) { -- ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 68, ret); -- dblayer_release_index_file(be, ai, db); -- return ret; -+ PR_ASSERT(txn == NULL); /* Apparently always called with null txn */ -+ /* Need txn / should be rw to update id2entry */ -+ ret = START_TXN(&txn, NULL, 0); -+ if (ret) { -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 60, ret); -+ return dbmdb_map_error(__FUNCTION__, ret); - } -- dbc = cursor.cur; -- txn = cursor.txn; - btxn.back_txn_txn = txn; -- ret = MDB_CURSOR_GET(dbc, &key, &data, MDB_FIRST); -+ /* Open cursor on the objectclass index */ -+ ret = dbmdb_open_subcount_cursor(be, SLAPI_ATTR_OBJECTCLASS, txn, &c_objectclass); -+ if (ret) { -+ if (ret != DBI_RC_NOTFOUND) { -+ /* No database ==> There is nothing to do. */ -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 61, ret); -+ } -+ return END_TXN(&txn, ret); -+ } -+ /* Open cursor on the entryrdn index */ -+ ret = dbmdb_open_subcount_cursor(be, LDBM_ENTRYRDN_STR, txn, &c_entryrdn); -+ if (ret) { -+ ldbm_nasty((char*)__FUNCTION__, sourcefile, 62, ret); -+ dbmdb_close_subcount_cursor(&c_objectclass); -+ return END_TXN(&txn, ret); -+ } - -- /* Walk along the index */ -- while (ret != MDB_NOTFOUND) { -+ /* Walk along C* keys (usually starting at C1) */ -+ key.mv_data = "C"; -+ key.mv_size = 1; -+ ret = MDB_CURSOR_GET(c_entryrdn.dbc, &key, &data, MDB_SET_RANGE); -+ while (ret == 0) { - size_t sub_count = 0; -+ size_t t_sub_count = 0; -+ MDB_val oldkey = key; - ID parentid = 0; - - if (0 != ret) { - key.mv_data=NULL; -- ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 62, ret); -+ ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 63, ret); - break; - } - /* check if we need to abort */ -@@ -247,33 +282,50 @@ dbmdb_update_subordinatecounts(backend *be, ImportJob *job, dbi_txn_t *txn) - key_count); - started_progress_logging = 1; - } -+ if (!key.mv_data || *(char *)key.mv_data != 'C') { -+ /* No more children */ -+ break; -+ } - -- if (*(char *)key.mv_data == EQ_PREFIX) { -- char tmp[11]; -- -- /* construct the parent's ID from the key */ -- if (key.mv_size >= sizeof tmp) { -+ /* construct the parent's ID from the key */ -+ if (key.mv_size >= sizeof tmp) { -+ ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 64, ret); -+ ret = DBI_RC_INVALID; -+ break; -+ } -+ /* Generate expected value for parentid */ -+ memcpy(tmp, key.mv_data, key.mv_size); -+ tmp[key.mv_size] = 0; -+ parentid = (ID)atol(tmp+1); -+ PR_ASSERT(0 != parentid); -+ oldkey = key; -+ /* Walk the entries having same key and check if they are tombstone */ -+ do { -+ /* Reorder data */ -+ ID old_data, new_data; -+ if (data.mv_size < sizeof old_data) { - ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 66, ret); -+ ret = DBI_RC_INVALID; - break; - } -- memcpy(tmp, key.mv_data, key.mv_size); -- tmp[key.mv_size] = 0; -- parentid = (ID)atol(tmp+1); -- PR_ASSERT(0 != parentid); -- /* Get number of records having the same key */ -- ret = mdb_cursor_count(dbc, &sub_count); -- if (ret) { -- ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 63, ret); -- break; -- } -- PR_ASSERT(0 != sub_count); -- ret = dbmdb_import_update_entry_subcount(be, parentid, sub_count, isencrypted, &btxn); -- if (ret) { -- ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 64, ret); -- break; -+ memcpy(&old_data, data.mv_data, sizeof old_data); -+ id_internal_to_stored(old_data, (char*)&new_data); -+ data.mv_data = &new_data; -+ data.mv_size = sizeof new_data; -+ if (!dbmdb_subcount_is_tombstone(&c_objectclass, &data)) { -+ sub_count++; -+ } else { -+ t_sub_count++; - } -+ ret = MDB_CURSOR_GET(c_entryrdn.dbc, &key, &data, MDB_NEXT); -+ } while (ret == 0 && key.mv_size == oldkey.mv_size && -+ memcmp(key.mv_data, oldkey.mv_data, key.mv_size) == 0); -+ ret2 = import_update_entry_subcount(be, parentid, sub_count, t_sub_count, isencrypted, &btxn); -+ if (ret2) { -+ ret = ret2; -+ ldbm_nasty("dbmdb_update_subordinatecounts", sourcefile, 65, ret); -+ break; - } -- ret = MDB_CURSOR_GET(dbc, &key, &data, MDB_NEXT_NODUP); - } - if (started_progress_logging) { - /* Finish what we started... */ -@@ -285,11 +337,13 @@ dbmdb_update_subordinatecounts(backend *be, ImportJob *job, dbi_txn_t *txn) - if (ret == MDB_NOTFOUND) { - ret = 0; - } -- -- dbmdb_close_cursor(&cursor, ret); -- dblayer_release_index_file(be, ai, db); -- -- return (ret); -+ dbmdb_close_subcount_cursor(&c_entryrdn); -+ dbmdb_close_subcount_cursor(&c_objectclass); -+ if (txn) { -+ return END_TXN(&txn, ret); -+ } else { -+ return ret; -+ } - } - - /* Function used to gather a list of indexed attrs */ -@@ -362,10 +416,6 @@ dbmdb_import_free_job(ImportJob *job) - slapi_ch_free((void **)&asabird); - } - job->index_list = NULL; -- if (NULL != job->mothers) { -- import_subcount_stuff_term(job->mothers); -- slapi_ch_free((void **)&job->mothers); -- } - - dbmdb_back_free_incl_excl(job->include_subtrees, job->exclude_subtrees); - -@@ -1244,7 +1294,6 @@ dbmdb_run_ldif2db(Slapi_PBlock *pb) - } - job->starting_ID = 1; - job->first_ID = 1; -- job->mothers = CALLOC(import_subcount_stuff); - - /* how much space should we allocate to index buffering? */ - job->job_index_buffer_size = dbmdb_import_get_index_buffer_size(); -@@ -1255,7 +1304,6 @@ dbmdb_run_ldif2db(Slapi_PBlock *pb) - (job->inst->inst_li->li_import_cachesize / 10) + (1024 * 1024); - PR_Unlock(job->inst->inst_li->li_config_mutex); - } -- import_subcount_stuff_init(job->mothers); - - if (job->task != NULL) { - /* count files, use that to track "progress" in cn=tasks */ -@@ -1382,7 +1430,6 @@ dbmdb_bulk_import_start(Slapi_PBlock *pb) - job->starting_ID = 1; - job->first_ID = 1; - -- job->mothers = CALLOC(import_subcount_stuff); - /* how much space should we allocate to index buffering? */ - job->job_index_buffer_size = dbmdb_import_get_index_buffer_size(); - if (job->job_index_buffer_size == 0) { -@@ -1390,7 +1437,6 @@ dbmdb_bulk_import_start(Slapi_PBlock *pb) - job->job_index_buffer_size = (job->inst->inst_li->li_dbcachesize / 10) + - (1024 * 1024); - } -- import_subcount_stuff_init(job->mothers); - dbmdb_import_init_writer(job, IM_BULKIMPORT); - - pthread_mutex_init(&job->wire_lock, NULL); -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 39d2b06f7..6978bf5e3 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 -@@ -2928,6 +2928,7 @@ dbmdb_add_op_attrs(ImportJob *job, struct backentry *ep, ID pid) - /* Get rid of attributes you're not allowed to specify yourself */ - slapi_entry_delete_values(ep->ep_entry, hassubordinates, NULL); - slapi_entry_delete_values(ep->ep_entry, numsubordinates, NULL); -+ slapi_entry_delete_values(ep->ep_entry, tombstone_numsubordinates, NULL); - - /* Upgrade DN format only */ - /* Set current parentid to e_aux_attrs to remove it from the index file. */ -diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c -index 1b161a578..682fd70e2 100644 ---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c -+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c -@@ -1336,7 +1336,7 @@ int dbmdb_open_cursor(dbmdb_cursor_t *dbicur, dbmdb_ctx_t *ctx, dbmdb_dbi_t *dbi - dbicur->dbi = dbi; - if (ctx->readonly) - flags |= MDB_RDONLY; -- rc = START_TXN(&dbicur->txn, NULL, 0); -+ rc = START_TXN(&dbicur->txn, NULL, ((flags&MDB_RDONLY) ? TXNFL_RDONLY : 0)); - if (rc) { - return rc; - } -diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h -index fe230d60e..9cecf3edb 100644 ---- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h -+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h -@@ -394,8 +394,6 @@ void dbmdb_restore_file_update(struct ldbminfo *li, const char *directory); - int dbmdb_import_file_init(ldbm_instance *inst); - void dbmdb_import_file_update(ldbm_instance *inst); - int dbmdb_import_file_check(ldbm_instance *inst); --int dbmdb_import_subcount_mother_init(import_subcount_stuff *mothers, ID parent_id, size_t count); --int dbmdb_import_subcount_mother_count(import_subcount_stuff *mothers, ID parent_id); - void dbmdb_import_configure_index_buffer_size(size_t size); - size_t dbmdb_import_get_index_buffer_size(void); - int dbmdb_ldbm_back_wire_import(Slapi_PBlock *pb); -diff --git a/ldap/servers/slapd/back-ldbm/import.c b/ldap/servers/slapd/back-ldbm/import.c -index 5a03bb533..f9a20051a 100644 ---- a/ldap/servers/slapd/back-ldbm/import.c -+++ b/ldap/servers/slapd/back-ldbm/import.c -@@ -241,3 +241,62 @@ wait_for_ref_count(Slapi_Counter *inst_ref_count) - /* Done waiting, return the current ref count */ - return slapi_counter_get_value(inst_ref_count); - } -+ -+/********** helper functions for importing **********/ -+ -+int -+import_update_entry_subcount(backend *be, ID parentid, size_t sub_count, size_t t_sub_count, int isencrypted, back_txn *txn) -+{ -+ ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; -+ int ret = 0; -+ modify_context mc = {0}; -+ char value_buffer[22] = {0}; /* enough digits for 2^64 children */ -+ char t_value_buffer[22] = {0}; /* enough digits for 2^64 children */ -+ struct backentry *e = NULL; -+ char *numsub_str = numsubordinates; -+ Slapi_Mods *smods = NULL; -+ static char *sourcefile = "import.c"; -+ -+ /* Get hold of the parent */ -+ e = id2entry(be, parentid, txn, &ret); -+ if ((NULL == e) || (0 != ret)) { -+ slapi_log_err(SLAPI_LOG_ERR, "import_update_entry_subcount", "failed to read entry with ID %d ret=%d\n", -+ parentid, ret); -+ ldbm_nasty("import_update_entry_subcount", sourcefile, 5, ret); -+ return (0 == ret) ? -1 : ret; -+ } -+ /* Lock it (not really required since we're single-threaded here, but -+ * let's do it so we can reuse the modify routines) */ -+ cache_lock_entry(&inst->inst_cache, e); -+ modify_init(&mc, e); -+ mc.attr_encrypt = isencrypted; -+ sprintf(value_buffer, "%lu", (long unsigned int)sub_count); -+ sprintf(t_value_buffer, "%lu", (long unsigned int)t_sub_count); -+ smods = slapi_mods_new(); -+ if (sub_count) { -+ slapi_mods_add(smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, numsub_str, -+ strlen(value_buffer), value_buffer); -+ } else { -+ /* Make sure that the attribute is deleted */ -+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, numsub_str, NULL); -+ } -+ if (t_sub_count) { -+ slapi_mods_add(smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, LDBM_TOMBSTONE_NUMSUBORDINATES_STR, -+ strlen(t_value_buffer), t_value_buffer); -+ } else { -+ /* Make sure that the attribute is deleted */ -+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, LDBM_TOMBSTONE_NUMSUBORDINATES_STR, NULL); -+ } -+ ret = modify_apply_mods(&mc, smods); /* smods passed in */ -+ if (0 == ret) { -+ /* This will correctly index subordinatecount: */ -+ ret = modify_update_all(be, NULL, &mc, txn); -+ if (0 == ret) { -+ modify_switch_entries(&mc, be); -+ } -+ } -+ /* entry is unlocked and returned to the cache in modify_term */ -+ modify_term(&mc, be); -+ return ret; -+} -+ -diff --git a/ldap/servers/slapd/back-ldbm/import.h b/ldap/servers/slapd/back-ldbm/import.h -index b3f6b7493..e066f4195 100644 ---- a/ldap/servers/slapd/back-ldbm/import.h -+++ b/ldap/servers/slapd/back-ldbm/import.h -@@ -117,7 +117,6 @@ typedef struct _ImportJob - * another pass */ - int uuid_gen_type; /* kind of uuid to generate */ - char *uuid_namespace; /* namespace for name-generated uuid */ -- import_subcount_stuff *mothers; - double average_progress_rate; - double recent_progress_rate; - double cache_hit_ratio; -@@ -209,6 +208,7 @@ struct _import_worker_info - /* import.c */ - void import_log_notice(ImportJob *job, int log_level, char *subsystem, char *format, ...); - int import_main_offline(void *arg); -+int import_update_entry_subcount(backend *be, ID parentid, size_t sub_count, size_t t_sub_count, int isencrypted, back_txn *txn); - - /* ldif2ldbm.c */ - void reset_progress(void); -diff --git a/ldap/servers/slapd/back-ldbm/ldif2ldbm.c b/ldap/servers/slapd/back-ldbm/ldif2ldbm.c -index 403ce6ae8..8b0386489 100644 ---- a/ldap/servers/slapd/back-ldbm/ldif2ldbm.c -+++ b/ldap/servers/slapd/back-ldbm/ldif2ldbm.c -@@ -54,49 +54,6 @@ typedef struct _export_args - /* static functions */ - - --/********** common routines for classic/deluxe import code **********/ -- --static PRIntn --import_subcount_hash_compare_keys(const void *v1, const void *v2) --{ -- return (((ID)((uintptr_t)v1) == (ID)((uintptr_t)v2)) ? 1 : 0); --} -- --static PRIntn --import_subcount_hash_compare_values(const void *v1, const void *v2) --{ -- return (((size_t)v1 == (size_t)v2) ? 1 : 0); --} -- --static PLHashNumber --import_subcount_hash_fn(const void *id) --{ -- return (PLHashNumber)((uintptr_t)id); --} -- --void --import_subcount_stuff_init(import_subcount_stuff *stuff) --{ -- stuff->hashtable = PL_NewHashTable(IMPORT_SUBCOUNT_HASHTABLE_SIZE, -- import_subcount_hash_fn, import_subcount_hash_compare_keys, -- import_subcount_hash_compare_values, NULL, NULL); --} -- --void --import_subcount_stuff_term(import_subcount_stuff *stuff) --{ -- if (stuff != NULL && stuff->hashtable != NULL) { -- PL_HashTableDestroy(stuff->hashtable); -- } --} -- -- -- --/********** functions for maintaining the subordinate count **********/ -- -- -- -- - /********** ldif2db entry point **********/ - - /* -diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py -index e6f9273eb..39a2852e5 100644 ---- a/src/lib389/lib389/__init__.py -+++ b/src/lib389/lib389/__init__.py -@@ -1835,7 +1835,7 @@ class DirSrv(SimpleLDAPObject, object): - one entry. - @param - entry dn - @param - search scope, in ldap.SCOPE_BASE (default), -- ldap.SCOPE_SUB, ldap.SCOPE_ONE -+ ldap.SCOPE_SUB, ldap.SCOPE_ONELEVEL - @param filterstr - filterstr, default '(objectClass=*)' from - SimpleLDAPObject - @param attrlist - list of attributes to retrieve. eg ['cn', 'uid'] -diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py -index 1f9f1556f..37277296d 100644 ---- a/src/lib389/lib389/_mapped_object.py -+++ b/src/lib389/lib389/_mapped_object.py -@@ -200,7 +200,7 @@ class DSLdapObject(DSLogging, DSLint): - if scope == 'base': - search_scope = ldap.SCOPE_BASE - elif scope == 'one': -- search_scope = ldap.SCOPE_ONE -+ search_scope = ldap.SCOPE_ONELEVEL - elif scope == 'subtree': - search_scope = ldap.SCOPE_SUBTREE - return _search_ext_s(self._instance,self._dn, search_scope, filter, --- -2.49.0 - diff --git a/0032-Issue-6910-Fix-latest-coverity-issues.patch b/0032-Issue-6910-Fix-latest-coverity-issues.patch deleted file mode 100644 index 7f9f8d5..0000000 --- a/0032-Issue-6910-Fix-latest-coverity-issues.patch +++ /dev/null @@ -1,559 +0,0 @@ -From dd40581c66c702a9a5d34ad1c498d8957be51f81 Mon Sep 17 00:00:00 2001 -From: Mark Reynolds -Date: Mon, 28 Jul 2025 17:12:33 -0400 -Subject: [PATCH] Issue 6910 - Fix latest coverity issues - -Description: - -Fix various coverity/ASAN warnings: - -- CID 1618831: Resource leak (RESOURCE_LEAK) - bdb_layer.c -- CID 1612606: Resource leak (RESOURCE_LEAK) - log.c -- CID 1611461: Uninitialized pointer read (UNINIT) - repl5_agmt.c -- CID 1568589: Dereference before null check (REVERSE_INULL) - repl5_agmt.c -- CID 1590353: Logically dead code (DEADCODE) - repl5_agmt.c -- CID 1611460: Logically dead code (DEADCODE) - control.c -- CID 1610568: Dereference after null check (FORWARD_NULL) - modify.c -- CID 1591259: Out-of-bounds read (OVERRUN) - memberof.c -- CID 1550231: Unsigned compared against 0 (NO_EFFECT) - memberof_config.c -- CID 1548904: Overflowed constant (INTEGER_OVERFLOW) - ch_malloc.c -- CID 1548902: Overflowed constant (INTEGER_OVERFLOW) - dse.lc -- CID 1548900: Overflowed return value (INTEGER_OVERFLOW) - acct_util.c -- CID 1548898: Overflowed constant (INTEGER_OVERFLOW) - parents.c -- CID 1546849: Resource leak (RESOURCE_LEAK) - referint.c -- ASAN - Use after free - automember.c - -Relates: http://github.com/389ds/389-ds-base/issues/6910 - -Reviewed by: progier & spichugi(Thanks!) ---- - ldap/servers/plugins/acctpolicy/acct_util.c | 6 ++- - ldap/servers/plugins/automember/automember.c | 9 +++-- - ldap/servers/plugins/memberof/memberof.c | 15 +++++-- - .../plugins/memberof/memberof_config.c | 24 ++++++------ - ldap/servers/plugins/referint/referint.c | 11 +++++- - ldap/servers/plugins/replication/repl5_agmt.c | 39 ++++++++----------- - .../slapd/back-ldbm/db-bdb/bdb_import.c | 5 ++- - .../back-ldbm/db-bdb/bdb_instance_config.c | 3 +- - .../slapd/back-ldbm/db-bdb/bdb_layer.c | 13 +++++-- - ldap/servers/slapd/back-ldbm/parents.c | 4 +- - ldap/servers/slapd/ch_malloc.c | 4 +- - ldap/servers/slapd/dse.c | 4 +- - ldap/servers/slapd/log.c | 5 ++- - ldap/servers/slapd/modify.c | 6 +-- - ldap/servers/slapd/passwd_extop.c | 2 +- - ldap/servers/slapd/unbind.c | 12 ++++-- - 16 files changed, 98 insertions(+), 64 deletions(-) - -diff --git a/ldap/servers/plugins/acctpolicy/acct_util.c b/ldap/servers/plugins/acctpolicy/acct_util.c -index b27eeaff1..7735d10e6 100644 ---- a/ldap/servers/plugins/acctpolicy/acct_util.c -+++ b/ldap/servers/plugins/acctpolicy/acct_util.c -@@ -17,7 +17,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - Contributors: - Hewlett-Packard Development Company, L.P. - --Copyright (C) 2021 Red Hat, Inc. -+Copyright (C) 2025 Red Hat, Inc. - ******************************************************************************/ - - #include -@@ -248,6 +248,10 @@ gentimeToEpochtime(char *gentimestr) - - /* Turn tm object into local epoch time */ - epochtime = mktime(&t); -+ if (epochtime == (time_t) -1) { -+ /* mktime failed */ -+ return 0; -+ } - - /* Turn local epoch time into GMT epoch time */ - epochtime -= zone_offset; -diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c -index f900db7f2..9eade495e 100644 ---- a/ldap/servers/plugins/automember/automember.c -+++ b/ldap/servers/plugins/automember/automember.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2022 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -1756,9 +1756,10 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char - - mod_pb = slapi_pblock_new(); - /* Do a single mod with error overrides for DEL/ADD */ -- result = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_dn_byval(group_dn), mods, -- automember_get_plugin_id(), 0); -- -+ Slapi_DN *sdn = slapi_sdn_new_normdn_byref(group_dn); -+ result = slapi_single_modify_internal_override(mod_pb, sdn, mods, -+ automember_get_plugin_id(), 0); -+ slapi_sdn_free(&sdn); - if(add){ - if (result != LDAP_SUCCESS) { - slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM, -diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c -index 3775e52c9..82cb60c96 100644 ---- a/ldap/servers/plugins/memberof/memberof.c -+++ b/ldap/servers/plugins/memberof/memberof.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2021 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -1657,6 +1657,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn - /* We already did the search for this backend, don't - * do it again when we fall through */ - do_suffix_search = PR_FALSE; -+ slapi_pblock_init(search_pb); - } - } - } else if (!all_backends) { -@@ -3755,6 +3756,10 @@ memberof_replace_list(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_DN *group_ - - pre_index++; - } else { -+ if (pre_index >= pre_total || post_index >= post_total) { -+ /* Don't overrun pre_array/post_array */ -+ break; -+ } - /* decide what to do */ - int cmp = memberof_compare( - config, -@@ -4445,10 +4450,12 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc) - - while (1) { - slapi_pblock_init(mod_pb); -- -+ Slapi_DN *sdn = slapi_sdn_new_normdn_byref(dn); - /* Internal mod with error overrides for DEL/ADD */ -- rc = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_normdn_byref(dn), single_mod, -- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS); -+ rc = slapi_single_modify_internal_override(mod_pb, sdn, single_mod, -+ memberof_get_plugin_id(), -+ SLAPI_OP_FLAG_BYPASS_REFERRALS); -+ slapi_sdn_free(&sdn); - if (rc == LDAP_OBJECT_CLASS_VIOLATION) { - if (!add_oc || added_oc) { - /* -diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c -index 964fcc2b8..e4da351d9 100644 ---- a/ldap/servers/plugins/memberof/memberof_config.c -+++ b/ldap/servers/plugins/memberof/memberof_config.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2021 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -568,28 +568,28 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)), - slapi_filter_free(theConfig.group_filter, 1); - - if (num_groupattrs > 1) { -- int bytes_out = 0; -- int filter_str_len = groupattr_name_len + (num_groupattrs * 4) + 4; -+ size_t bytes_out = 0; -+ size_t filter_str_len = groupattr_name_len + (num_groupattrs * 4) + 4; -+ int32_t rc = 0; - - /* Allocate enough space for the filter */ - filter_str = slapi_ch_malloc(filter_str_len); - - /* Add beginning of filter. */ -- bytes_out = snprintf(filter_str, filter_str_len - bytes_out, "(|"); -- if (bytes_out<0) { -- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, -- "snprintf unexpectly failed in memberof_apply_config.\n"); -+ rc = snprintf(filter_str, filter_str_len - bytes_out, "(|"); -+ if (rc < 0) { -+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, "snprintf unexpectly failed in memberof_apply_config.\n"); - *returncode = LDAP_UNWILLING_TO_PERFORM; - goto done; -+ } else { -+ bytes_out = rc; - } - - /* Add filter section for each groupattr. */ -- for (i = 0; theConfig.groupattrs && theConfig.groupattrs[i]; i++) { -- size_t bytes_read = snprintf(filter_str + bytes_out, filter_str_len - bytes_out, "(%s=*)", -- theConfig.groupattrs[i]); -+ for (size_t i=0; theConfig.groupattrs && theConfig.groupattrs[i]; i++) { -+ int32_t bytes_read = snprintf(filter_str + bytes_out, filter_str_len - bytes_out, "(%s=*)", theConfig.groupattrs[i]); - if (bytes_read<0) { -- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, -- "snprintf unexpectly failed in memberof_apply_config.\n"); -+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, "snprintf unexpectly failed in memberof_apply_config.\n"); - *returncode = LDAP_UNWILLING_TO_PERFORM; - goto done; - } -diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c -index a2f2e4706..cf79f973e 100644 ---- a/ldap/servers/plugins/referint/referint.c -+++ b/ldap/servers/plugins/referint/referint.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) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -1492,6 +1492,15 @@ referint_thread_func(void *arg __attribute__((unused))) - } - - ptoken = ldap_utf8strtok_r(NULL, delimiter, &iter); -+ if (ptoken == NULL) { -+ /* Invalid line in referint log, skip it */ -+ slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, -+ "Skipping invalid referint log line: (%s)\n", thisline); -+ slapi_sdn_free(&sdn); -+ continue; -+ } -+ -+ slapi_sdn_free(&tmpsuperior); - if (!strcasecmp(ptoken, "NULL")) { - tmpsuperior = NULL; - } else { -diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c -index c6cfcda07..9b2d82547 100644 ---- a/ldap/servers/plugins/replication/repl5_agmt.c -+++ b/ldap/servers/plugins/replication/repl5_agmt.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) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -2628,31 +2628,26 @@ agmt_update_init_status(Repl_Agmt *ra) - mod_idx++; - } - -- if (nb_mods) { -- /* it is ok to release the lock here because we are done with the agreement data. -- we have to do it before issuing the modify operation because it causes -- agmtlist_notify_all to be called which uses the same lock - hence the deadlock */ -- PR_Unlock(ra->lock); -- -- pb = slapi_pblock_new(); -- mods[nb_mods] = NULL; -+ /* it is ok to release the lock here because we are done with the agreement data. -+ we have to do it before issuing the modify operation because it causes -+ agmtlist_notify_all to be called which uses the same lock - hence the deadlock */ -+ PR_Unlock(ra->lock); - -- slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL, -- repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0); -- slapi_modify_internal_pb(pb); -+ pb = slapi_pblock_new(); -+ mods[nb_mods] = NULL; - -- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); -- if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) { -- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmt_update_consumer_ruv - " -- "%s: agmt_update_consumer_ruv: " -- "failed to update consumer's RUV; LDAP error - %d\n", -- ra->long_name, rc); -- } -+ slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL, -+ repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0); -+ slapi_modify_internal_pb(pb); - -- slapi_pblock_destroy(pb); -- } else { -- PR_Unlock(ra->lock); -+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); -+ if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) { -+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmt_update_consumer_ruv - " -+ "%s: agmt_update_consumer_ruv: failed to update consumer's RUV; LDAP error - %d\n", -+ ra->long_name, rc); - } -+ -+ slapi_pblock_destroy(pb); - slapi_ch_free((void **)&mods); - slapi_mod_done(&smod_start_time); - slapi_mod_done(&smod_end_time); -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 39edb7d0e..2bb6b0267 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2020 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -947,6 +947,7 @@ bdb_ancestorid_new_idl_create_index(backend *be, ImportJob *job) - EQ_PREFIX, (u_long)id); - key.size++; /* include the null terminator */ - ret = NEW_IDL_NO_ALLID; -+ idl_free(&children); - children = idl_fetch(be, db_pid, &key, txn, ai_pid, &ret); - if (ret != 0) { - ldbm_nasty("bdb_ancestorid_new_idl_create_index", sourcefile, 13070, ret); -@@ -957,6 +958,7 @@ bdb_ancestorid_new_idl_create_index(backend *be, ImportJob *job) - if (job->flags & FLAG_ABORT) { - import_log_notice(job, SLAPI_LOG_ERR, "bdb_ancestorid_new_idl_create_index", - "ancestorid creation aborted."); -+ idl_free(&children); - ret = -1; - break; - } -@@ -1290,6 +1292,7 @@ bdb_update_subordinatecounts(backend *be, ImportJob *job, DB_TXN *txn) - } - bdb_close_subcount_cursor(&c_entryrdn); - bdb_close_subcount_cursor(&c_objectclass); -+ - return ret; - } - -diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c -index bb515a23f..44a624fde 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2020 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -261,6 +261,7 @@ bdb_instance_cleanup(struct ldbm_instance *inst) - if (inst_dirp && *inst_dir) { - return_value = env->remove(env, inst_dirp, 0); - } else { -+ slapi_ch_free((void **)&env); - return_value = -1; - } - if (return_value == EBUSY) { -diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c -index 4f069197e..e21f418be 100644 ---- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c -+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2023 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -2034,9 +2034,13 @@ bdb_pre_close(struct ldbminfo *li) - conf = (bdb_config *)li->li_dblayer_config; - bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env; - -+ if (pEnv == NULL) { -+ return; -+ } -+ - pthread_mutex_lock(&pEnv->bdb_thread_count_lock); - -- if (conf->bdb_stop_threads || !pEnv) { -+ if (conf->bdb_stop_threads) { - /* already stopped. do nothing... */ - goto timeout_escape; - } -@@ -2210,6 +2214,7 @@ bdb_remove_env(struct ldbminfo *li) - } - if (NULL == li) { - slapi_log_err(SLAPI_LOG_ERR, "bdb_remove_env", "No ldbm info is given\n"); -+ slapi_ch_free((void **)&env); - return -1; - } - -@@ -2219,10 +2224,11 @@ bdb_remove_env(struct ldbminfo *li) - if (rc) { - slapi_log_err(SLAPI_LOG_ERR, - "bdb_remove_env", "Failed to remove DB environment files. " -- "Please remove %s/__db.00# (# is 1 through 6)\n", -+ "Please remove %s/__db.00# (# is 1 through 6)\n", - home_dir); - } - } -+ slapi_ch_free((void **)&env); - return rc; - } - -@@ -6359,6 +6365,7 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info) - db->close(db, 0); - rc = bdb_db_remove_ex((bdb_db_env *)priv->dblayer_env, path, NULL, PR_TRUE); - inst->inst_changelog = NULL; -+ slapi_ch_free_string(&path); - slapi_ch_free_string(&instancedir); - } - } -diff --git a/ldap/servers/slapd/back-ldbm/parents.c b/ldap/servers/slapd/back-ldbm/parents.c -index 31107591e..52c665ca4 100644 ---- a/ldap/servers/slapd/back-ldbm/parents.c -+++ b/ldap/servers/slapd/back-ldbm/parents.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) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -123,7 +123,7 @@ parent_update_on_childchange(modify_context *mc, int op, size_t *new_sub_count) - /* Now compute the new value */ - if ((PARENTUPDATE_ADD == op) || (PARENTUPDATE_RESURECT == op)) { - current_sub_count++; -- } else { -+ } else if (current_sub_count > 0) { - current_sub_count--; - } - -diff --git a/ldap/servers/slapd/ch_malloc.c b/ldap/servers/slapd/ch_malloc.c -index 75e791135..bacbc9371 100644 ---- a/ldap/servers/slapd/ch_malloc.c -+++ b/ldap/servers/slapd/ch_malloc.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) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -234,7 +234,7 @@ slapi_ch_bvecdup(struct berval **v) - ++i; - newberval = (struct berval **)slapi_ch_malloc((i + 1) * sizeof(struct berval *)); - newberval[i] = NULL; -- while (i-- > 0) { -+ while (i > 0 && i-- > 0) { - newberval[i] = slapi_ch_bvdup(v[i]); - } - } -diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c -index 0f266f0d7..a0db367b2 100644 ---- a/ldap/servers/slapd/dse.c -+++ b/ldap/servers/slapd/dse.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) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -637,7 +637,7 @@ dse_updateNumSubordinates(Slapi_Entry *entry, int op) - /* Now compute the new value */ - if (SLAPI_OPERATION_ADD == op) { - current_sub_count++; -- } else { -+ } else if (current_sub_count > 0) { - current_sub_count--; - } - { -diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c -index 178d29b89..58f9fb4d6 100644 ---- a/ldap/servers/slapd/log.c -+++ b/ldap/servers/slapd/log.c -@@ -1,6 +1,6 @@ - /** BEGIN COPYRIGHT BLOCK - * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. -- * Copyright (C) 2005-2024 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * Copyright (C) 2010 Hewlett-Packard Development Company, L.P. - * All rights reserved. - * -@@ -199,6 +199,7 @@ 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); - return -1; - } -@@ -209,11 +210,13 @@ compress_log_file(char *log_name, int32_t mode) - if (bytes_written == 0) - { - fclose(source); -+ /* coverity[leaked_storage] gzclose does close FD */ - gzclose(outfile); - 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 */ -diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c -index 0e2abea18..b0066faf8 100644 ---- a/ldap/servers/slapd/modify.c -+++ b/ldap/servers/slapd/modify.c -@@ -1,6 +1,6 @@ - /** BEGIN COPYRIGHT BLOCK - * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. -- * Copyright (C) 2009 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * Copyright (C) 2009, 2010 Hewlett-Packard Development Company, L.P. - * All rights reserved. - * -@@ -498,7 +498,7 @@ slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod - * - * Any other errors encountered during the operation will be returned as-is. - */ --int -+int - slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_id, int op_flags) - { - int rc = 0; -@@ -512,7 +512,7 @@ slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDA - !pb ? "pb " : "", - !sdn ? "sdn " : "", - !mod ? "mod " : "", -- !mod[0] ? "mod[0] " : ""); -+ !mod || !mod[0] ? "mod[0] " : ""); - - return LDAP_PARAM_ERROR; - } -diff --git a/ldap/servers/slapd/passwd_extop.c b/ldap/servers/slapd/passwd_extop.c -index 0296d64fb..3ade0be7f 100644 ---- a/ldap/servers/slapd/passwd_extop.c -+++ b/ldap/servers/slapd/passwd_extop.c -@@ -1,5 +1,5 @@ - /** BEGIN COPYRIGHT BLOCK -- * Copyright (C) 2005 Red Hat, Inc. -+ * Copyright (C) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -diff --git a/ldap/servers/slapd/unbind.c b/ldap/servers/slapd/unbind.c -index 89f6ef932..d0562a7c9 100644 ---- a/ldap/servers/slapd/unbind.c -+++ b/ldap/servers/slapd/unbind.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) 2025 Red Hat, Inc. - * All rights reserved. - * - * License: GPL (version 3 or any later version). -@@ -87,8 +87,12 @@ do_unbind(Slapi_PBlock *pb) - /* pass the unbind to all backends */ - be_unbindall(pb_conn, operation); - --free_and_return:; -+free_and_return: - -- /* close the connection to the client */ -- disconnect_server(pb_conn, operation->o_connid, operation->o_opid, SLAPD_DISCONNECT_UNBIND, 0); -+ /* close the connection to the client after refreshing the operation */ -+ slapi_pblock_get(pb, SLAPI_OPERATION, &operation); -+ disconnect_server(pb_conn, -+ operation ? operation->o_connid : -1, -+ operation ? operation->o_opid : -1, -+ SLAPD_DISCONNECT_UNBIND, 0); - } --- -2.49.0 - diff --git a/0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch b/0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch deleted file mode 100644 index f3dbf20..0000000 --- a/0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch +++ /dev/null @@ -1,35 +0,0 @@ -From b79da81cd24edd12af1da894d6dbd6f08995bc9d Mon Sep 17 00:00:00 2001 -From: Viktor Ashirov -Date: Mon, 11 Aug 2025 13:19:13 +0200 -Subject: [PATCH] Issue 6929 - Compilation failure with rust-1.89 on Fedora ELN - -Bug Description: -The `ValueArrayRefIter` struct has a lifetime parameter `'a`. -But in the `iter` method the return type doesn't specify the lifetime parameter. - -Fix Description: -Make the lifetime explicit. - -Fixes: https://github.com/389ds/389-ds-base/issues/6929 - -Reviewed by: @droideck (Thanks!) ---- - src/slapi_r_plugin/src/value.rs | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs -index 2fd35c808..fec74ac25 100644 ---- a/src/slapi_r_plugin/src/value.rs -+++ b/src/slapi_r_plugin/src/value.rs -@@ -61,7 +61,7 @@ impl ValueArrayRef { - ValueArrayRef { raw_slapi_val } - } - -- pub fn iter(&self) -> ValueArrayRefIter { -+ pub fn iter(&self) -> ValueArrayRefIter<'_> { - ValueArrayRefIter { - idx: 0, - va_ref: &self, --- -2.49.0 - diff --git a/389-ds-base-git-local.sh b/389-ds-base-git-local.sh deleted file mode 100644 index bc809cb..0000000 --- a/389-ds-base-git-local.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -DATE=`date +%Y%m%d` -# use a real tag name here -VERSION=1.3.5.14 -PKGNAME=389-ds-base -TAG=${TAG:-$PKGNAME-$VERSION} -#SRCNAME=$PKGNAME-$VERSION-$DATE -SRCNAME=$PKGNAME-$VERSION - -test -d .git || { - echo you must be in the ds git repo to use this - echo bye - exit 1 -} - -if [ -z "$1" ] ; then - dir=. -else - dir="$1" -fi - -git archive --prefix=$SRCNAME/ $TAG | bzip2 > $dir/$SRCNAME.tar.bz2 diff --git a/389-ds-base-git.sh b/389-ds-base-git.sh deleted file mode 100644 index 0043901..0000000 --- a/389-ds-base-git.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -DATE=`date +%Y%m%d` -# use a real tag name here -VERSION=1.3.5.14 -PKGNAME=389-ds-base -TAG=${TAG:-$PKGNAME-$VERSION} -URL="https://git.fedorahosted.org/git/?p=389/ds.git;a=snapshot;h=$TAG;sf=tgz" -SRCNAME=$PKGNAME-$VERSION - -wget -O $SRCNAME.tar.gz "$URL" - -echo convert tgz format to tar.bz2 format - -gunzip $PKGNAME-$VERSION.tar.gz -bzip2 $PKGNAME-$VERSION.tar diff --git a/389-ds-base.spec b/389-ds-base.spec index cc48695..e477152 100644 --- a/389-ds-base.spec +++ b/389-ds-base.spec @@ -46,9 +46,9 @@ ExcludeArch: i686 Summary: 389 Directory Server (base) Name: 389-ds-base -Version: 2.7.0 -Release: 7%{?dist} -License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (0BSD OR Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT OR Zlib) 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 +Version: 2.8.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 (CC-BY-4.0 AND MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR CC0-1.0) AND (MIT OR Unlicense) AND 0BSD AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND MIT AND MIT AND ISC AND MPL-2.0 AND PSF-2.0 AND Zlib URL: https://www.port389.org Conflicts: selinux-policy-base < 3.9.8 Conflicts: freeipa-server < 4.0.3 @@ -58,18 +58,15 @@ Obsoletes: %{name}-legacy-tools-debuginfo < 1.4.4.6 Provides: ldif2ldbm >= 0 ##### Bundled cargo crates list - START ##### -Provides: bundled(crate(addr2line)) = 0.24.2 -Provides: bundled(crate(adler2)) = 2.0.1 Provides: bundled(crate(allocator-api2)) = 0.2.21 Provides: bundled(crate(atty)) = 0.2.14 Provides: bundled(crate(autocfg)) = 1.5.0 -Provides: bundled(crate(backtrace)) = 0.3.75 Provides: bundled(crate(base64)) = 0.13.1 -Provides: bundled(crate(bitflags)) = 2.9.1 +Provides: bundled(crate(bitflags)) = 2.10.0 Provides: bundled(crate(byteorder)) = 1.5.0 Provides: bundled(crate(cbindgen)) = 0.26.0 -Provides: bundled(crate(cc)) = 1.2.31 -Provides: bundled(crate(cfg-if)) = 1.0.1 +Provides: bundled(crate(cc)) = 1.2.52 +Provides: bundled(crate(cfg-if)) = 1.0.4 Provides: bundled(crate(clap)) = 3.2.25 Provides: bundled(crate(clap_lex)) = 0.2.4 Provides: bundled(crate(concread)) = 0.5.7 @@ -77,84 +74,266 @@ Provides: bundled(crate(crossbeam-epoch)) = 0.9.18 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.13 +Provides: bundled(crate(errno)) = 0.3.14 Provides: bundled(crate(fastrand)) = 2.3.0 Provides: bundled(crate(fernet)) = 0.1.4 +Provides: bundled(crate(find-msvc-tools)) = 0.1.7 Provides: bundled(crate(foldhash)) = 0.1.5 Provides: bundled(crate(foreign-types)) = 0.3.2 Provides: bundled(crate(foreign-types-shared)) = 0.1.1 -Provides: bundled(crate(getrandom)) = 0.3.3 -Provides: bundled(crate(gimli)) = 0.31.1 -Provides: bundled(crate(hashbrown)) = 0.15.4 +Provides: bundled(crate(getrandom)) = 0.3.4 +Provides: bundled(crate(hashbrown)) = 0.15.5 Provides: bundled(crate(heck)) = 0.4.1 Provides: bundled(crate(hermit-abi)) = 0.1.19 Provides: bundled(crate(indexmap)) = 1.9.3 -Provides: bundled(crate(io-uring)) = 0.7.9 -Provides: bundled(crate(itoa)) = 1.0.15 -Provides: bundled(crate(jobserver)) = 0.1.33 -Provides: bundled(crate(libc)) = 0.2.174 -Provides: bundled(crate(linux-raw-sys)) = 0.9.4 -Provides: bundled(crate(log)) = 0.4.27 +Provides: bundled(crate(itoa)) = 1.0.17 +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.13.0 -Provides: bundled(crate(memchr)) = 2.7.5 -Provides: bundled(crate(miniz_oxide)) = 0.8.9 -Provides: bundled(crate(mio)) = 1.0.4 -Provides: bundled(crate(object)) = 0.36.7 +Provides: bundled(crate(memchr)) = 2.7.6 Provides: bundled(crate(once_cell)) = 1.21.3 -Provides: bundled(crate(openssl)) = 0.10.73 +Provides: bundled(crate(openssl)) = 0.10.75 Provides: bundled(crate(openssl-macros)) = 0.1.1 -Provides: bundled(crate(openssl-sys)) = 0.9.109 +Provides: bundled(crate(openssl-sys)) = 0.9.111 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(proc-macro-hack)) = 0.5.20+deprecated -Provides: bundled(crate(proc-macro2)) = 1.0.95 -Provides: bundled(crate(quote)) = 1.0.40 +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(rustc-demangle)) = 0.1.26 -Provides: bundled(crate(rustix)) = 1.0.8 -Provides: bundled(crate(ryu)) = 1.0.20 -Provides: bundled(crate(serde)) = 1.0.219 -Provides: bundled(crate(serde_derive)) = 1.0.219 -Provides: bundled(crate(serde_json)) = 1.0.142 +Provides: bundled(crate(rustix)) = 1.1.3 +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(slab)) = 0.4.10 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.104 -Provides: bundled(crate(tempfile)) = 3.20.0 +Provides: bundled(crate(syn)) = 2.0.114 +Provides: bundled(crate(tempfile)) = 3.24.0 Provides: bundled(crate(termcolor)) = 1.4.1 Provides: bundled(crate(textwrap)) = 0.16.2 -Provides: bundled(crate(tokio)) = 1.47.1 +Provides: bundled(crate(tokio)) = 1.49.0 Provides: bundled(crate(toml)) = 0.5.11 -Provides: bundled(crate(tracing)) = 0.1.41 -Provides: bundled(crate(tracing-attributes)) = 0.1.30 -Provides: bundled(crate(tracing-core)) = 0.1.34 -Provides: bundled(crate(unicode-ident)) = 1.0.18 +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(uuid)) = 0.8.2 Provides: bundled(crate(vcpkg)) = 0.2.15 -Provides: bundled(crate(wasi)) = 0.14.2+wasi_0.2.4 +Provides: bundled(crate(wasi)) = 0.11.1+wasi_snapshot_preview1 +Provides: bundled(crate(wasip2)) = 1.0.1+wasi_0.2.4 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.9 +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.1.3 -Provides: bundled(crate(windows-sys)) = 0.60.2 -Provides: bundled(crate(windows-targets)) = 0.53.3 -Provides: bundled(crate(windows_aarch64_gnullvm)) = 0.53.0 -Provides: bundled(crate(windows_aarch64_msvc)) = 0.53.0 -Provides: bundled(crate(windows_i686_gnu)) = 0.53.0 -Provides: bundled(crate(windows_i686_gnullvm)) = 0.53.0 -Provides: bundled(crate(windows_i686_msvc)) = 0.53.0 -Provides: bundled(crate(windows_x86_64_gnu)) = 0.53.0 -Provides: bundled(crate(windows_x86_64_gnullvm)) = 0.53.0 -Provides: bundled(crate(windows_x86_64_msvc)) = 0.53.0 -Provides: bundled(crate(wit-bindgen-rt)) = 0.39.0 -Provides: bundled(crate(zeroize)) = 1.8.1 -Provides: bundled(crate(zeroize_derive)) = 1.4.2 +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(zeroize)) = 1.8.2 +Provides: bundled(crate(zeroize_derive)) = 1.4.3 +Provides: bundled(crate(zmij)) = 1.0.12 +Provides: bundled(npm(@eslint-community/eslint-utils)) = 4.4.1 +Provides: bundled(npm(@eslint-community/regexpp)) = 4.12.1 +Provides: bundled(npm(@eslint/eslintrc)) = 2.1.4 +Provides: bundled(npm(@eslint/js)) = 8.57.1 +Provides: bundled(npm(@fortawesome/fontawesome-common-types)) = 0.2.36 +Provides: bundled(npm(@fortawesome/fontawesome-svg-core)) = 1.2.36 +Provides: bundled(npm(@fortawesome/free-solid-svg-icons)) = 5.15.4 +Provides: bundled(npm(@fortawesome/react-fontawesome)) = 0.1.19 +Provides: bundled(npm(@humanwhocodes/config-array)) = 0.13.0 +Provides: bundled(npm(@humanwhocodes/module-importer)) = 1.0.1 +Provides: bundled(npm(@humanwhocodes/object-schema)) = 2.0.3 +Provides: bundled(npm(@nodelib/fs.scandir)) = 2.1.5 +Provides: bundled(npm(@nodelib/fs.stat)) = 2.0.5 +Provides: bundled(npm(@nodelib/fs.walk)) = 1.2.8 +Provides: bundled(npm(@patternfly/patternfly)) = 5.4.1 +Provides: bundled(npm(@patternfly/react-charts)) = 7.4.3 +Provides: bundled(npm(@patternfly/react-core)) = 5.4.1 +Provides: bundled(npm(@patternfly/react-log-viewer)) = 5.3.0 +Provides: bundled(npm(@patternfly/react-styles)) = 5.4.0 +Provides: bundled(npm(@patternfly/react-table)) = 5.4.1 +Provides: bundled(npm(@types/d3-array)) = 3.2.1 +Provides: bundled(npm(@types/d3-color)) = 3.1.3 +Provides: bundled(npm(@types/d3-ease)) = 3.0.2 +Provides: bundled(npm(@types/d3-interpolate)) = 3.0.4 +Provides: bundled(npm(@types/d3-path)) = 3.1.1 +Provides: bundled(npm(@types/d3-scale)) = 4.0.9 +Provides: bundled(npm(@types/d3-shape)) = 3.1.7 +Provides: bundled(npm(@types/d3-time)) = 3.0.4 +Provides: bundled(npm(@types/d3-timer)) = 3.0.2 +Provides: bundled(npm(@ungap/structured-clone)) = 1.3.0 +Provides: bundled(npm(@xterm/addon-canvas)) = 0.7.0 +Provides: bundled(npm(@xterm/xterm)) = 5.5.0 +Provides: bundled(npm(acorn)) = 8.14.0 +Provides: bundled(npm(acorn-jsx)) = 5.3.2 +Provides: bundled(npm(ajv)) = 6.12.6 +Provides: bundled(npm(ansi-regex)) = 5.0.1 +Provides: bundled(npm(ansi-styles)) = 4.3.0 +Provides: bundled(npm(argparse)) = 2.0.1 +Provides: bundled(npm(attr-accept)) = 2.2.5 +Provides: bundled(npm(autolinker)) = 3.16.2 +Provides: bundled(npm(balanced-match)) = 1.0.2 +Provides: bundled(npm(brace-expansion)) = 1.1.12 +Provides: bundled(npm(callsites)) = 3.1.0 +Provides: bundled(npm(chalk)) = 4.1.2 +Provides: bundled(npm(color-convert)) = 2.0.1 +Provides: bundled(npm(color-name)) = 1.1.4 +Provides: bundled(npm(concat-map)) = 0.0.1 +Provides: bundled(npm(core-util-is)) = 1.0.3 +Provides: bundled(npm(cross-spawn)) = 7.0.6 +Provides: bundled(npm(d3-array)) = 3.2.4 +Provides: bundled(npm(d3-color)) = 3.1.0 +Provides: bundled(npm(d3-ease)) = 3.0.1 +Provides: bundled(npm(d3-format)) = 3.1.0 +Provides: bundled(npm(d3-interpolate)) = 3.0.1 +Provides: bundled(npm(d3-path)) = 3.1.0 +Provides: bundled(npm(d3-scale)) = 4.0.2 +Provides: bundled(npm(d3-shape)) = 3.2.0 +Provides: bundled(npm(d3-time)) = 3.1.0 +Provides: bundled(npm(d3-time-format)) = 4.1.0 +Provides: bundled(npm(d3-timer)) = 3.0.1 +Provides: bundled(npm(debug)) = 4.4.0 +Provides: bundled(npm(deep-is)) = 0.1.4 +Provides: bundled(npm(delaunator)) = 4.0.1 +Provides: bundled(npm(delaunay-find)) = 0.0.6 +Provides: bundled(npm(dequal)) = 2.0.3 +Provides: bundled(npm(doctrine)) = 3.0.0 +Provides: bundled(npm(encoding)) = 0.1.13 +Provides: bundled(npm(escape-string-regexp)) = 4.0.0 +Provides: bundled(npm(eslint)) = 8.57.1 +Provides: bundled(npm(eslint-plugin-react-hooks)) = 4.6.2 +Provides: bundled(npm(eslint-scope)) = 7.2.2 +Provides: bundled(npm(eslint-visitor-keys)) = 3.4.3 +Provides: bundled(npm(espree)) = 9.6.1 +Provides: bundled(npm(esquery)) = 1.6.0 +Provides: bundled(npm(esrecurse)) = 4.3.0 +Provides: bundled(npm(estraverse)) = 5.3.0 +Provides: bundled(npm(esutils)) = 2.0.3 +Provides: bundled(npm(fast-deep-equal)) = 3.1.3 +Provides: bundled(npm(fast-json-stable-stringify)) = 2.1.0 +Provides: bundled(npm(fast-levenshtein)) = 2.0.6 +Provides: bundled(npm(fastq)) = 1.19.0 +Provides: bundled(npm(file-entry-cache)) = 6.0.1 +Provides: bundled(npm(file-selector)) = 2.1.2 +Provides: bundled(npm(find-up)) = 5.0.0 +Provides: bundled(npm(flat-cache)) = 3.2.0 +Provides: bundled(npm(flatted)) = 3.3.3 +Provides: bundled(npm(focus-trap)) = 7.5.4 +Provides: bundled(npm(fs.realpath)) = 1.0.0 +Provides: bundled(npm(gettext-parser)) = 2.1.0 +Provides: bundled(npm(glob)) = 7.2.3 +Provides: bundled(npm(glob-parent)) = 6.0.2 +Provides: bundled(npm(globals)) = 13.24.0 +Provides: bundled(npm(graphemer)) = 1.4.0 +Provides: bundled(npm(has-flag)) = 4.0.0 +Provides: bundled(npm(hoist-non-react-statics)) = 3.3.2 +Provides: bundled(npm(iconv-lite)) = 0.6.3 +Provides: bundled(npm(ignore)) = 5.3.2 +Provides: bundled(npm(import-fresh)) = 3.3.1 +Provides: bundled(npm(imurmurhash)) = 0.1.4 +Provides: bundled(npm(inflight)) = 1.0.6 +Provides: bundled(npm(inherits)) = 2.0.4 +Provides: bundled(npm(internmap)) = 2.0.3 +Provides: bundled(npm(is-extglob)) = 2.1.1 +Provides: bundled(npm(is-glob)) = 4.0.3 +Provides: bundled(npm(is-path-inside)) = 3.0.3 +Provides: bundled(npm(isarray)) = 1.0.0 +Provides: bundled(npm(isexe)) = 2.0.0 +Provides: bundled(npm(js-sha1)) = 0.7.0 +Provides: bundled(npm(js-sha256)) = 0.11.0 +Provides: bundled(npm(js-tokens)) = 4.0.0 +Provides: bundled(npm(js-yaml)) = 4.1.1 +Provides: bundled(npm(json-buffer)) = 3.0.1 +Provides: bundled(npm(json-schema-traverse)) = 0.4.1 +Provides: bundled(npm(json-stable-stringify-without-jsonify)) = 1.0.1 +Provides: bundled(npm(json-stringify-safe)) = 5.0.1 +Provides: bundled(npm(keyv)) = 4.5.4 +Provides: bundled(npm(levn)) = 0.4.1 +Provides: bundled(npm(locate-path)) = 6.0.0 +Provides: bundled(npm(lodash)) = 4.17.21 +Provides: bundled(npm(lodash.merge)) = 4.6.2 +Provides: bundled(npm(loose-envify)) = 1.4.0 +Provides: bundled(npm(memoize-one)) = 5.2.1 +Provides: bundled(npm(minimatch)) = 3.1.2 +Provides: bundled(npm(ms)) = 2.1.3 +Provides: bundled(npm(natural-compare)) = 1.4.0 +Provides: bundled(npm(object-assign)) = 4.1.1 +Provides: bundled(npm(once)) = 1.4.0 +Provides: bundled(npm(optionator)) = 0.9.4 +Provides: bundled(npm(p-limit)) = 3.1.0 +Provides: bundled(npm(p-locate)) = 5.0.0 +Provides: bundled(npm(parent-module)) = 1.0.1 +Provides: bundled(npm(path-exists)) = 4.0.0 +Provides: bundled(npm(path-is-absolute)) = 1.0.1 +Provides: bundled(npm(path-key)) = 3.1.1 +Provides: bundled(npm(prelude-ls)) = 1.2.1 +Provides: bundled(npm(process-nextick-args)) = 2.0.1 +Provides: bundled(npm(prop-types)) = 15.8.1 +Provides: bundled(npm(punycode)) = 2.3.1 +Provides: bundled(npm(queue-microtask)) = 1.2.3 +Provides: bundled(npm(react)) = 18.3.1 +Provides: bundled(npm(react-dom)) = 18.3.1 +Provides: bundled(npm(react-dropzone)) = 14.3.8 +Provides: bundled(npm(react-fast-compare)) = 3.2.2 +Provides: bundled(npm(react-is)) = 16.13.1 +Provides: bundled(npm(readable-stream)) = 2.3.8 +Provides: bundled(npm(remarkable)) = 2.0.1 +Provides: bundled(npm(resolve-from)) = 4.0.0 +Provides: bundled(npm(reusify)) = 1.0.4 +Provides: bundled(npm(rimraf)) = 3.0.2 +Provides: bundled(npm(run-parallel)) = 1.2.0 +Provides: bundled(npm(safe-buffer)) = 5.2.1 +Provides: bundled(npm(safer-buffer)) = 2.1.2 +Provides: bundled(npm(scheduler)) = 0.23.2 +Provides: bundled(npm(shebang-command)) = 2.0.0 +Provides: bundled(npm(shebang-regex)) = 3.0.0 +Provides: bundled(npm(sprintf-js)) = 1.0.3 +Provides: bundled(npm(string_decoder)) = 1.1.1 +Provides: bundled(npm(strip-ansi)) = 6.0.1 +Provides: bundled(npm(strip-json-comments)) = 3.1.1 +Provides: bundled(npm(supports-color)) = 7.2.0 +Provides: bundled(npm(tabbable)) = 6.2.0 +Provides: bundled(npm(text-table)) = 0.2.0 +Provides: bundled(npm(throttle-debounce)) = 5.0.2 +Provides: bundled(npm(tslib)) = 2.8.1 +Provides: bundled(npm(type-check)) = 0.4.0 +Provides: bundled(npm(type-fest)) = 0.20.2 +Provides: bundled(npm(uri-js)) = 4.4.1 +Provides: bundled(npm(util-deprecate)) = 1.0.2 +Provides: bundled(npm(uuid)) = 10.0.0 +Provides: bundled(npm(victory-area)) = 37.3.6 +Provides: bundled(npm(victory-axis)) = 37.3.6 +Provides: bundled(npm(victory-bar)) = 37.3.6 +Provides: bundled(npm(victory-box-plot)) = 37.3.6 +Provides: bundled(npm(victory-brush-container)) = 37.3.6 +Provides: bundled(npm(victory-chart)) = 37.3.6 +Provides: bundled(npm(victory-core)) = 37.3.6 +Provides: bundled(npm(victory-create-container)) = 37.3.6 +Provides: bundled(npm(victory-cursor-container)) = 37.3.6 +Provides: bundled(npm(victory-group)) = 37.3.6 +Provides: bundled(npm(victory-legend)) = 37.3.6 +Provides: bundled(npm(victory-line)) = 37.3.6 +Provides: bundled(npm(victory-pie)) = 37.3.6 +Provides: bundled(npm(victory-polar-axis)) = 37.3.6 +Provides: bundled(npm(victory-scatter)) = 37.3.6 +Provides: bundled(npm(victory-selection-container)) = 37.3.6 +Provides: bundled(npm(victory-shared-events)) = 37.3.6 +Provides: bundled(npm(victory-stack)) = 37.3.6 +Provides: bundled(npm(victory-tooltip)) = 37.3.6 +Provides: bundled(npm(victory-vendor)) = 37.3.6 +Provides: bundled(npm(victory-voronoi-container)) = 37.3.6 +Provides: bundled(npm(victory-zoom-container)) = 37.3.6 +Provides: bundled(npm(which)) = 2.0.2 +Provides: bundled(npm(word-wrap)) = 1.2.5 +Provides: bundled(npm(wrappy)) = 1.0.2 +Provides: bundled(npm(yocto-queue)) = 0.1.0 ##### Bundled cargo crates list - END ##### BuildRequires: nspr-devel >= 4.32 @@ -277,9 +456,7 @@ Requires: perl-sigtrap Obsoletes: %{name} <= 1.3.5.4 -Source0: https://releases.pagure.org/389-ds-base/%{name}-%{version}.tar.bz2 -# 389-ds-git.sh should be used to generate the source tarball from git -Source1: %{name}-git.sh +Source0: https://github.com/389ds/%{name}/releases/download/%{name}-%{version}/%{name}-%{version}.tar.bz2 Source2: %{name}-devel.README %if %{bundle_jemalloc} Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2 @@ -289,39 +466,10 @@ Source4: 389-ds-base.sysusers Source5: vendor-%{version}-1.tar.gz Source6: Cargo-%{version}-1.lock -Patch: 0001-Issue-6377-syntax-error-in-setup.py-6378.patch -Patch: 0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch -Patch: 0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch -Patch: 0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch -Patch: 0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch -Patch: 0006-Issue-6782-Improve-paged-result-locking.patch -Patch: 0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch -Patch: 0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch -Patch: 0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch -Patch: 0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch -Patch: 0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch -Patch: 0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch -Patch: 0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch -Patch: 0014-Issue-6893-Log-user-that-is-updated-during-password-.patch -Patch: 0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch -Patch: 0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch -Patch: 0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch -Patch: 0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch -Patch: 0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch -Patch: 0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch -Patch: 0021-Issue-6468-CLI-Fix-default-error-log-level.patch -Patch: 0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch -Patch: 0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch -Patch: 0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch -Patch: 0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch -Patch: 0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch -Patch: 0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch -Patch: 0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch -Patch: 0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch -Patch: 0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch -Patch: 0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch -Patch: 0032-Issue-6910-Fix-latest-coverity-issues.patch -Patch: 0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch +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 %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -425,9 +573,13 @@ A cockpit UI Plugin for configuring and administering the 389 Directory Server %prep %autosetup -p1 -n %{name}-%{version} +%if %{defined SOURCE5} rm -rf vendor tar xzf %{SOURCE5} +%endif +%if %{defined SOURCE6} cp %{SOURCE6} src/Cargo.lock +%endif %if %{bundle_jemalloc} %setup -q -n %{name}-%{version} -T -D -b 3 @@ -768,6 +920,32 @@ exit 0 %endif %changelog +* Fri Jan 09 2026 Viktor Ashirov - 2.8.0-1 +- Resolves: RHEL-111229 - Error showing local password policy on web UI [rhel-9] +- Resolves: RHEL-112680 - Statistics about index lookup report a wrong duration [rhel-9] +- Resolves: RHEL-116426 - RetroCL plugin generates invalid LDIF [rhel-9] +- Resolves: RHEL-117050 - Replication online reinitialization of a large database gets stalled. [rhel-9] +- Resolves: RHEL-117748 - The numSubordinates value is not matching the number of direct children. [rhel-9] +- Resolves: RHEL-117771 - When deferred memberof update is enabled after the server crashed it should not launch memberof fixup task by default +- Resolves: RHEL-117782 - Ignore the memberOfDeferredUpdate setting when LMDB is used. [rhel-9] +- Resolves: RHEL-121170 - Units for changing MDB max size are not consistent across different tools [rhel-9] +- Resolves: RHEL-123231 - Improve the way to detect asynchronous operations in the access logs [rhel-9] +- Resolves: RHEL-123244 - Attribute uniqueness is not enforced upon modrdn operation [rhel-9] +- Resolves: RHEL-123258 - Typo in errors log after a Memberof fixup task. [rhel-9] +- Resolves: RHEL-123272 - LDAP high CPU usage while handling indexes with IDL scan limit at INT_MAX [rhel-9] +- Resolves: RHEL-123279 - The new ipahealthcheck test ipahealthcheck.ds.backends.BackendsCheck raises CRITICAL issue [rhel-9] +- Resolves: RHEL-123368 - IPA health check up script shows time skew is over 24 hours [rhel-9] +- Resolves: RHEL-123766 - 389-ds-base OpenScanHub Leaks Detected [rhel-9] +- Resolves: RHEL-123893 - Improve output dsctl dbverify when backend does not exist [rhel-9] +- Resolves: RHEL-123897 - [WebUI] Replication tab crashes after enabling replication as a consumer [rhel-9] +- Resolves: RHEL-123923 - Changelog trimming - add number of scanned entries to the log [rhel-9] +- Resolves: RHEL-126552 - RHDS 12.6 doesn't handle 'ldapsearch' filter with space char in DN name correctly [rhel-9] +- Resolves: RHEL-129559 - Online initialization of consumers fails with error -23 [rhel-9] +- Resolves: RHEL-129580 - Fix paged result search locking [rhel-9] +- Resolves: RHEL-138481 - Memory leak observed in ns-slapd with 389-ds-base-2.6.1-12 [rhel-9] +- Resolves: RHEL-139825 - Rebase 389-ds-base to 2.8.x +- Resolves: RHEL-140089 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication [rhel-9] + * Tue Sep 16 2025 Viktor Ashirov - 2.7.0-7 - Resolves: RHEL-104591 - RHDS12: Web console doesn't show Server Version [rhel-9] - Resolves: RHEL-104593 - The numSubordinates value is not matching the number of direct children. [rhel-9] diff --git a/main.fmf b/main.fmf index 338f547..0470ee9 100644 --- a/main.fmf +++ b/main.fmf @@ -10,7 +10,7 @@ package: [389-ds-base, git, pytest] - name: clone repo how: shell - script: git clone -b 389-ds-base-3.0 https://github.com/389ds/389-ds-base /root/ds + script: git clone -b 389-ds-base-2.8 https://github.com/389ds/389-ds-base /root/ds /test: /upstream_basic: test: pytest -v /root/ds/dirsrvtests/tests/suites/basic/basic_test.py diff --git a/sources b/sources index 90475a6..8fcf39d 100644 --- a/sources +++ b/sources @@ -1,4 +1,4 @@ SHA512 (jemalloc-5.3.0.tar.bz2) = 22907bb052096e2caffb6e4e23548aecc5cc9283dce476896a2b1127eee64170e3562fa2e7db9571298814a7a2c7df6e8d1fbe152bd3f3b0c1abec22a2de34b1 -SHA512 (389-ds-base-2.7.0.tar.bz2) = 3aa6ea8b2c59c6188ede25a0c027fc32153d5276857699bc43368db1cb747fea09edeff326186d30fda30fe0a98574625ae0907d99065d1868b169256de1b035 -SHA512 (Cargo-2.7.0-1.lock) = ea6db252e49de8aa2fe165f5cc773dc2eb227100d56953a36ca062680a3fc54870a961b05aaac1f7a761c69f3685cc8a7be474ac92377a1219c293fd1117f491 -SHA512 (vendor-2.7.0-1.tar.gz) = dda0afe82289812440c3e89ddd305ba21bf89104aa7debfabd398d9719c8e0b48173f52b487f270b9cc40c0b010dc1e749eb7f3a709074a0d650bea811e0e87a +SHA512 (389-ds-base-2.8.0.tar.bz2) = 82a1d71618dafa2aaba9e0c54d049dfda0a404fc836887e6283a22eded00a564d66c2988f19c549041db85511a3baa4792d68e89b2de8a5b3647c703c65b75cd +SHA512 (Cargo-2.8.0-1.lock) = aeeb596559cbda3752f3800934e78d50f8729bd3afc11be84105d85216ccdc67ad87cdd2871dcef1cc757745fb4d85b5f6298a6858893e5d6af1fe49b9a5eb59 +SHA512 (vendor-2.8.0-1.tar.gz) = a0b2c75bec490d8150f168fadae03f594d9c33dbd331bbce23a092e02dc946c6adc3341339017aeeb2de71eb07940be6df036b6426cbebbaa92b13d5fa9eeb9a