diff --git a/.389-ds-base.metadata b/.389-ds-base.metadata index a112433..20bf01f 100644 --- a/.389-ds-base.metadata +++ b/.389-ds-base.metadata @@ -1,3 +1,3 @@ -e9ce5b0affef3f7a319958610c5382152f1b559f SOURCES/389-ds-base-2.7.0.tar.bz2 +14e38ca3a1851bec5023c191a756c3c58b15f5e0 SOURCES/389-ds-base-2.8.0.tar.bz2 1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2 -b183c1ebee9c1d81d4b394df6de6521a8b333cbc SOURCES/vendor-2.7.0-1.tar.gz +04340b73d0c8dcc03e2fd08948334f51ffbd49d7 SOURCES/vendor-2.8.0-5.tar.gz diff --git a/.gitignore b/.gitignore index af6149d..f5d42f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -SOURCES/389-ds-base-2.7.0.tar.bz2 +SOURCES/389-ds-base-2.8.0.tar.bz2 SOURCES/jemalloc-5.3.0.tar.bz2 -SOURCES/vendor-2.7.0-1.tar.gz +SOURCES/vendor-2.8.0-5.tar.gz diff --git a/SOURCES/0001-Issue-6377-syntax-error-in-setup.py-6378.patch b/SOURCES/0001-Issue-6377-syntax-error-in-setup.py-6378.patch deleted file mode 100644 index 84ff8d0..0000000 --- a/SOURCES/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/SOURCES/0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch b/SOURCES/0001-Issue-7049-RetroCL-plugin-generates-invalid-LDIF.patch new file mode 100644 index 0000000..f4ae039 --- /dev/null +++ b/SOURCES/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/SOURCES/0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch b/SOURCES/0002-Issue-6838-lib389-replica.py-is-using-nonexistent-da.patch deleted file mode 100644 index 5a04986..0000000 --- a/SOURCES/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/SOURCES/0002-Issue-7096-During-replication-online-total-init-the-.patch b/SOURCES/0002-Issue-7096-During-replication-online-total-init-the-.patch new file mode 100644 index 0000000..4d86c59 --- /dev/null +++ b/SOURCES/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/SOURCES/0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch b/SOURCES/0003-Issue-6680-instance-read-only-mode-is-broken-6681.patch deleted file mode 100644 index ac0af7b..0000000 --- a/SOURCES/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/SOURCES/0003-Issue-Revise-paged-result-search-locking.patch b/SOURCES/0003-Issue-Revise-paged-result-search-locking.patch new file mode 100644 index 0000000..3e0fcc8 --- /dev/null +++ b/SOURCES/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/SOURCES/0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch b/SOURCES/0004-Issue-6825-RootDN-Access-Control-Plugin-with-wildcar.patch deleted file mode 100644 index 67ee402..0000000 --- a/SOURCES/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/SOURCES/0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch b/SOURCES/0005-Issue-6119-Synchronise-accept_thread-with-slapd_daem.patch deleted file mode 100644 index 8aa4af0..0000000 --- a/SOURCES/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/SOURCES/0005-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch b/SOURCES/0005-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch new file mode 100644 index 0000000..cec44d4 --- /dev/null +++ b/SOURCES/0005-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch @@ -0,0 +1,67 @@ +From cae81cde000df9a28733c673ee7c9b8292d69ef3 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Mon, 12 Jan 2026 10:58:02 +0100 +Subject: [PATCH] Issue 7172 - (2nd) Index ordering mismatch after upgrade + (#7180) + +Commit 742c12e0247ab64e87da000a4de2f3e5c99044ab introduced a regression +where the check to skip creating parentid/ancestorid indexes if they +already exist was incorrect. +The `ainfo_get()` function falls back to returning +LDBM_PSEUDO_ATTR_DEFAULT attrinfo when the requested attribute is not +found. +Since LDBM_PSEUDO_ATTR_DEFAULT is created before the ancestorid check, +`ainfo_get()` returns LDBM_PSEUDO_ATTR_DEFAULT instead of NULL, causing +the ancestorid index creation to be skipped entirely. + +When operations later try to use the ancestorid index, they fall back to +LDBM_PSEUDO_ATTR_DEFAULT, and attempting to open the .default dbi +mid-transaction fails with MDB_NOTFOUND (-30798). + +Fix Description: +Instead of just checking if `ainfo_get()` returns non-NULL, verify that +the returned attrinfo is actually for the requested attribute. + +Fixes: https://github.com/389ds/389-ds-base/issues/7172 + +Reviewed by: @tbordaz (Thanks!) +--- + ldap/servers/slapd/back-ldbm/instance.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c +index 30db99c81..23bd70243 100644 +--- a/ldap/servers/slapd/back-ldbm/instance.c ++++ b/ldap/servers/slapd/back-ldbm/instance.c +@@ -191,7 +191,7 @@ ldbm_instance_create_default_indexes(backend *be) + char *ancestorid_indexes_limit = NULL; + char *parentid_indexes_limit = NULL; + struct attrinfo *ai = NULL; +- struct attrinfo *index_already_configured = NULL; ++ int index_already_configured = 0; + struct index_idlistsizeinfo *iter; + int cookie; + int limit; +@@ -257,7 +257,8 @@ ldbm_instance_create_default_indexes(backend *be) + } + + ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai); +- index_already_configured = ai; ++ /* Check if the attrinfo is actually for parentid, not a fallback to .default */ ++ index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0); + if (!index_already_configured) { + e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit); + ldbm_instance_config_add_index_entry(inst, e, flags); +@@ -302,7 +303,8 @@ ldbm_instance_create_default_indexes(backend *be) + * but we still want to use the attr index file APIs. + */ + ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai); +- index_already_configured = ai; ++ /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */ ++ index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0); + if (!index_already_configured) { + e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit); + ldbm_instance_config_add_index_entry(inst, e, flags); +-- +2.52.0 + diff --git a/SOURCES/0006-Issue-6782-Improve-paged-result-locking.patch b/SOURCES/0006-Issue-6782-Improve-paged-result-locking.patch deleted file mode 100644 index 5c9558b..0000000 --- a/SOURCES/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/SOURCES/0006-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch b/SOURCES/0006-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch new file mode 100644 index 0000000..010e099 --- /dev/null +++ b/SOURCES/0006-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch @@ -0,0 +1,235 @@ +From c5aab4f8ba822572daa9ef69a0109577bf2147e1 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Tue, 20 Jan 2026 09:52:47 +0100 +Subject: [PATCH] Issue 7189 - DSBLE0007 generates incorrect remediation + commands for scan limits + +Bug Description: + +The generated dsconf commands for fixing missing system indexes had two issues: + +1. The --add-scanlimit value was not quoted, causing the shell to interpret + "limit=5000 type=eq flags=AND" as multiple arguments instead of a single + value, resulting in "unrecognized arguments: type=eq flags=AND" error. + +2. When both matching rule and scanlimit were missing, two separate commands + were generated where the second would fail because the matching rule was + already added by the first command. + +Fix Description: + +1. Quote the scanlimit value in all remediation commands + +2. Combine matching rule and scanlimit fixes into a single command when + both are missing for the same index instead of expected_scanlimit) + +Fixes: https://github.com/389ds/389-ds-base/issues/7189 + +Reviewed by: @progier389, @droideck (Thanks!) +--- + .../healthcheck/health_system_indexes_test.py | 126 ++++++++++++++++++ + src/lib389/lib389/backend.py | 39 +++--- + 2 files changed, 147 insertions(+), 18 deletions(-) + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 6293340ca..5eadf6283 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -405,6 +405,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) + + ++def test_missing_scanlimit(topology_st, log_buffering_enabled): ++ """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit ++ ++ :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86 ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Remove nsIndexIDListScanLimit from parentId index ++ 3. Use healthcheck without --json option ++ 4. Use healthcheck with --json option ++ 5. Verify the remediation command has properly quoted scanlimit ++ 6. Re-add the scanlimit ++ 7. Use healthcheck without --json option ++ 8. Use healthcheck with --json option ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. healthcheck reports DSBLE0007 code and related details ++ 4. healthcheck reports DSBLE0007 code and related details ++ 5. The scanlimit value is quoted in the remediation command ++ 6. Success ++ 7. healthcheck reports no issues found ++ 8. healthcheck reports no issues found ++ """ ++ ++ RET_CODE = "DSBLE0007" ++ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" ++ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND" ++ ++ standalone = topology_st.standalone ++ ++ log.info("Remove nsIndexIDListScanLimit from parentId index") ++ parentid_index = Index(standalone, PARENTID_DN) ++ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE) ++ ++ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE) ++ ++ # Verify the remediation command has properly quoted scanlimit ++ args = FakeArgs() ++ args.instance = standalone.serverid ++ args.verbose = standalone.verbose ++ args.list_errors = False ++ args.list_checks = False ++ args.exclude_check = [] ++ args.check = ["backends"] ++ args.dry_run = False ++ args.json = False ++ health_check_run(standalone, topology_st.logcap.log, args) ++ # Check that the scanlimit is quoted in the output ++ assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"') ++ log.info("Verified scanlimit is properly quoted in remediation command") ++ topology_st.logcap.flush() ++ ++ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE) ++ ++ log.info("Re-add the nsIndexIDListScanLimit") ++ parentid_index = Index(standalone, PARENTID_DN) ++ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE) ++ ++ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) ++ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) ++ ++ ++def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled): ++ """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing ++ ++ :id: af8214ad-5e4c-422a-8f74-3e99227551df ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index ++ 3. Use healthcheck and verify a single combined command is generated ++ 4. Re-add the matching rule and scanlimit ++ 5. Use healthcheck without --json option ++ 6. Use healthcheck with --json option ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit ++ 4. Success ++ 5. healthcheck reports no issues found ++ 6. healthcheck reports no issues found ++ """ ++ ++ RET_CODE = "DSBLE0007" ++ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" ++ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND" ++ ++ standalone = topology_st.standalone ++ ++ log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index") ++ parentid_index = Index(standalone, PARENTID_DN) ++ parentid_index.remove("nsMatchingRule", "integerOrderingMatch") ++ parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE) ++ ++ # Run healthcheck and verify combined command ++ args = FakeArgs() ++ args.instance = standalone.serverid ++ args.verbose = standalone.verbose ++ args.list_errors = False ++ args.list_checks = False ++ args.exclude_check = [] ++ args.check = ["backends"] ++ args.dry_run = False ++ args.json = False ++ health_check_run(standalone, topology_st.logcap.log, args) ++ ++ # Verify DSBLE0007 is reported ++ assert topology_st.logcap.contains(RET_CODE) ++ log.info("healthcheck returned code: %s" % RET_CODE) ++ ++ # Verify a single combined command is generated with both --add-mr and --add-scanlimit ++ assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"') ++ log.info("Verified combined command with both --add-mr and --add-scanlimit") ++ ++ topology_st.logcap.flush() ++ ++ log.info("Re-add the integerOrderingMatch matching rule and scanlimit") ++ parentid_index = Index(standalone, PARENTID_DN) ++ parentid_index.add("nsMatchingRule", "integerOrderingMatch") ++ parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE) ++ ++ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) ++ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) ++ ++ + def test_multiple_missing_indexes(topology_st, log_buffering_enabled): + """Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing + +diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py +index 024de2adb..d3c9ccf35 100644 +--- a/src/lib389/lib389/backend.py ++++ b/src/lib389/lib389/backend.py +@@ -678,7 +678,7 @@ class Backend(DSLdapObject): + if expected_config.get('matching_rule'): + cmd += f" --matching-rule {expected_config['matching_rule']}" + if expected_config.get('scanlimit'): +- cmd += f" --add-scanlimit {expected_config['scanlimit']}" ++ cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\"" + remediation_commands.append(cmd) + reindex_attrs.add(attr_name) # New index needs reindexing + else: +@@ -700,28 +700,31 @@ class Backend(DSLdapObject): + remediation_commands.append(cmd) + reindex_attrs.add(attr_name) + +- # Check matching rules ++ # Check matching rules and scanlimit together to generate a single combined command + expected_mr = expected_config.get('matching_rule') ++ expected_scanlimit = expected_config.get('scanlimit') ++ ++ missing_mr = False + if expected_mr: + actual_mrs_lower = [mr.lower() for mr in actual_mrs] + if expected_mr.lower() not in actual_mrs_lower: + discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}") +- # Add the missing matching rule +- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}" +- remediation_commands.append(cmd) +- reindex_attrs.add(attr_name) +- +- # Check fine grain definitions for parentid ONLY +- expected_scanlimit = expected_config.get('scanlimit') +- if (attr_name.lower() == "parentid") and expected_scanlimit and (len(actual_scanlimit) == 0): +- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_mr}") +- # Add the missing scanlimit +- if expected_mr: +- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr} --add-scanlimit {expected_scanlimit}" +- else: +- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-scanlimit {expected_scanlimit}" +- remediation_commands.append(cmd) +- reindex_attrs.add(attr_name) ++ missing_mr = True ++ ++ missing_scanlimit = False ++ if expected_scanlimit and (len(actual_scanlimit) == 0): ++ discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}") ++ missing_scanlimit = True ++ ++ # Generate a single combined command for all missing items ++ if missing_mr or missing_scanlimit: ++ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}" ++ if missing_mr: ++ cmd += f" --add-mr {expected_mr}" ++ if missing_scanlimit: ++ cmd += f" --add-scanlimit \"{expected_scanlimit}\"" ++ remediation_commands.append(cmd) ++ reindex_attrs.add(attr_name) + + except Exception as e: + self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}") +-- +2.52.0 + diff --git a/SOURCES/0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch b/SOURCES/0007-Issue-6822-Backend-creation-cleanup-and-Database-UI-.patch deleted file mode 100644 index cd0029d..0000000 --- a/SOURCES/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/SOURCES/0007-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch b/SOURCES/0007-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch new file mode 100644 index 0000000..8b06dee --- /dev/null +++ b/SOURCES/0007-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch @@ -0,0 +1,48 @@ +From 327ebf93decaa19bd3919afaeba0dc122eef1990 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 12 Jan 2026 13:53:05 -0500 +Subject: [PATCH] Issue 7184 - argparse.HelpFormatter _format_actions_usage() + is deprecated + +Description: + +_format_actions_usage() was removed in python 3.15. Instead we can use +_get_actions_usage_parts() but it also behaves differently between +python 3.14 and 3.15 so we need special handling. + +Relates: https://github.com/389ds/389-ds-base/issues/7184 + +Reviewed by: spichugi(Thanks!) +--- + src/lib389/lib389/cli_base/__init__.py | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py +index 06b8f9964..f1055aadc 100644 +--- a/src/lib389/lib389/cli_base/__init__.py ++++ b/src/lib389/lib389/cli_base/__init__.py +@@ -413,7 +413,20 @@ class CustomHelpFormatter(argparse.HelpFormatter): + + def _format_usage(self, usage, actions, groups, prefix): + usage = super(CustomHelpFormatter, self)._format_usage(usage, actions, groups, prefix) +- formatted_options = self._format_actions_usage(parent_arguments, []) ++ ++ if sys.version_info < (3, 13): ++ # Use _format_actions_usage() for Python 3.12 and earlier ++ formatted_options = self._format_actions_usage(parent_arguments, []) ++ else: ++ # Use _get_actions_usage_parts() for Python 3.13 and later ++ action_parts = self._get_actions_usage_parts(parent_arguments, []) ++ if sys.version_info >= (3, 15): ++ # Python 3.15 returns a tuple (list of actions, count of actions) ++ formatted_options = ' '.join(action_parts[0]) ++ else: ++ # Python 3.13 and 3.14 return a list of actions ++ formatted_options = ' '.join(action_parts) ++ + # If formatted_options already in usage - remove them + if formatted_options in usage: + usage = usage.replace(f' {formatted_options}', '') +-- +2.52.0 + diff --git a/SOURCES/0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch b/SOURCES/0008-Issue-6857-uiduniq-allow-specifying-match-rules-in-t.patch deleted file mode 100644 index 5d62b75..0000000 --- a/SOURCES/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/SOURCES/0008-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch b/SOURCES/0008-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch new file mode 100644 index 0000000..ec16ee6 --- /dev/null +++ b/SOURCES/0008-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch @@ -0,0 +1,53 @@ +From dbabbe6a3c42b979d14960b355925c9748371cad Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Fri, 30 Jan 2026 12:00:13 +0100 +Subject: [PATCH] Issue 7027 - (2nd) 389-ds-base OpenScanHub Leaks Detected + (#7211) + +Fix Description: +Update coverity annotations. + +Relates: https://github.com/389ds/389-ds-base/issues/7027 + +Reviewed by: @aadhikar (Thanks!) +--- + ldap/servers/slapd/log.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c +index 668a02454..6f57a3d9c 100644 +--- a/ldap/servers/slapd/log.c ++++ b/ldap/servers/slapd/log.c +@@ -203,8 +203,8 @@ compress_log_file(char *log_name, int32_t mode) + + if ((source = fopen(log_name, "r")) == NULL) { + /* Failed to open log file */ +- /* coverity[leaked_storage] gzclose does close FD */ + gzclose(outfile); ++ /* coverity[leaked_handle] gzclose does close FD */ + return -1; + } + +@@ -214,17 +214,17 @@ compress_log_file(char *log_name, int32_t mode) + if (bytes_written == 0) + { + fclose(source); +- /* coverity[leaked_storage] gzclose does close FD */ + gzclose(outfile); ++ /* coverity[leaked_handle] gzclose does close FD */ + return -1; + } + bytes_read = fread(buf, 1, LOG_CHUNK, source); + } +- /* coverity[leaked_storage] gzclose does close FD */ + gzclose(outfile); + fclose(source); + PR_Delete(log_name); /* remove the old uncompressed log */ + ++ /* coverity[leaked_handle] gzclose does close FD */ + return 0; + } + +-- +2.52.0 + diff --git a/SOURCES/0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch b/SOURCES/0009-Issue-6756-CLI-UI-Properly-handle-disabled-NDN-cache.patch deleted file mode 100644 index f519007..0000000 --- a/SOURCES/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/SOURCES/0009-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch b/SOURCES/0009-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch new file mode 100644 index 0000000..229a8d4 --- /dev/null +++ b/SOURCES/0009-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch @@ -0,0 +1,197 @@ +From 39d47ca91e866d30b35dca41d477ad4ee07f8a14 Mon Sep 17 00:00:00 2001 +From: progier389 +Date: Mon, 2 Feb 2026 15:39:18 +0100 +Subject: [PATCH] Issue 7213 - MDB_BAD_VALSIZE error while handling VLV (#7214) + +* Issue 7213 - MDB_BAD_VALSIZE error while handling VLV +Avoid failing lmdb operation when handling VLV index by truncating the key so that key+data is small enough. + +Issue: #7213 + +Reviewed by: @mreynolds389 , @vashirov (Thanks!) + +Assisted by: Claude A/I + +(cherry picked from commit 5ebce22d4214bec5ed94ad84c4448164be99389a) +--- + .../tests/suites/vlv/regression_test.py | 110 ++++++++++++++++++ + .../slapd/back-ldbm/db-mdb/mdb_layer.c | 5 + + ldap/servers/slapd/back-ldbm/vlv.c | 7 +- + 3 files changed, 121 insertions(+), 1 deletion(-) + +diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py +index c8db94b19..1cc03c303 100644 +--- a/dirsrvtests/tests/suites/vlv/regression_test.py ++++ b/dirsrvtests/tests/suites/vlv/regression_test.py +@@ -1178,6 +1178,116 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr): + + + ++def test_vlv_long_attribute_value(topology_st, request): ++ """ ++ Test VLV with an entry containing a very long attribute value (2K). ++ ++ :id: 99126fa4-003e-11f1-b7d6-c85309d5c3e3 ++ :setup: Standalone instance. ++ :steps: ++ 1. Cleanup leftover from previous tests ++ 2. Create VLV search and index on cn attribute ++ 3. Reindex VLV ++ 4. Add an entry with a cn attribute having 2K character value ++ 5. Verify the entry was added successfully ++ 6. Perform a VLV search to ensure it still works ++ 7. Add another entry with a cn attribute having 2K character value ++ 8. Verify the entry was added successfully ++ 9. Perform a VLV search to ensure it still works ++ :expectedresults: ++ 1. Should Success. ++ 2. Should Success. ++ 3. Should Success. ++ 4. Should Success. ++ 5. Should Success. ++ 6. Should Success. ++ 7. Should Success. ++ 8. Should Success. ++ 9. Should Success. ++ """ ++ inst = topology_st.standalone ++ reindex_task = Tasks(inst) ++ ++ users_to_delete = [] ++ ++ def fin(): ++ cleanup(inst) ++ # Clean the added users ++ for user in users_to_delete: ++ user.delete() ++ ++ if not DEBUGGING: ++ request.addfinalizer(fin) ++ ++ # Clean previous tests leftover ++ fin() ++ ++ # Create VLV search and index ++ vlv_search, vlv_index = create_vlv_search_and_index(inst) ++ assert reindex_task.reindex( ++ suffix=DEFAULT_SUFFIX, ++ attrname=vlv_index.rdn, ++ args={TASK_WAIT: True}, ++ vlv=True ++ ) == 0 ++ ++ # Add a few regular users first ++ add_users(inst, 10) ++ ++ # Create a very long cn value (2K characters) ++ long_cn_value = 'a' * 2048 + '1' ++ ++ # Add an entry with the long cn attribute ++ users = UserAccounts(inst, DEFAULT_SUFFIX) ++ user_properties = { ++ 'uid': 'longcnuser1', ++ 'cn': long_cn_value, ++ 'sn': 'user1', ++ 'uidNumber': '99999', ++ 'gidNumber': '99999', ++ 'homeDirectory': '/home/longcnuser1' ++ } ++ user = users.create(properties=user_properties) ++ users_to_delete.append(user); ++ ++ # Verify the entry was created and has the long cn value ++ entry = user.get_attr_vals_utf8('cn') ++ assert entry[0] == long_cn_value ++ log.info(f'Successfully created user with cn length: {len(entry[0])}') ++ ++ # Perform VLV search to ensure VLV still works with long attribute values ++ conn = open_new_ldapi_conn(inst.serverid) ++ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)")) ++ assert count > 0 ++ log.info(f'VLV search successful with {count} entries including entry with 2K cn value') ++ ++ # Add another entry with the long cn attribute ++ long_cn_value = 'a' * 2048 + '2' ++ ++ user_properties = { ++ 'uid': 'longcnuser2', ++ 'cn': long_cn_value, ++ 'sn': 'user2', ++ 'uidNumber': '99998', ++ 'gidNumber': '99998', ++ 'homeDirectory': '/home/longcnuser2' ++ } ++ user = users.create(properties=user_properties) ++ users_to_delete.append(user); ++ ++ # Verify the entry was created and has the long cn value ++ entry = user.get_attr_vals_utf8('cn') ++ assert entry[0] == long_cn_value ++ log.info(f'Successfully created user with cn length: {len(entry[0])}') ++ ++ # Perform VLV search to ensure VLV still works with long attribute values ++ conn = open_new_ldapi_conn(inst.serverid) ++ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)")) ++ assert count > 1 ++ log.info(f'VLV search successful with {count} entries including entry with 2K cn value') ++ ++ ++ + if __name__ == "__main__": + # Run isolated + # -s for DEBUG mode +diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c +index c6e9f8b01..ffbd6609f 100644 +--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c ++++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c +@@ -2138,10 +2138,15 @@ void *dbmdb_recno_cache_build(void *arg) + recno = 1; + } + while (rc == 0) { ++ struct ldbminfo *li = (struct ldbminfo *)rcctx->cursor->be->be_database->plg_private; + slapi_log_err(SLAPI_LOG_DEBUG, "dbmdb_recno_cache_build", "recno=%d\n", recno); + if (recno % RECNO_CACHE_INTERVAL == 1) { + /* Prepare the cache data */ + len = sizeof(*rce) + data.mv_size + key.mv_size; ++ if (len > li->li_max_key_len) { ++ key.mv_size = li->li_max_key_len - data.mv_size - sizeof(*rce); ++ len = li->li_max_key_len; ++ } + rce = (dbmdb_recno_cache_elmt_t*)slapi_ch_malloc(len); + rce->len = len; + rce->recno = recno; +diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c +index 18431799c..a1cd226f4 100644 +--- a/ldap/servers/slapd/back-ldbm/vlv.c ++++ b/ldap/servers/slapd/back-ldbm/vlv.c +@@ -866,6 +866,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct + struct vlv_key *key = NULL; + dbi_val_t data = {0}; + dblayer_private *priv = NULL; ++ size_t key_size_limit = li->li_max_key_len - sizeof(entry->ep_id); + + slapi_pblock_get(pb, SLAPI_BACKEND, &be); + priv = (dblayer_private *)li->li_dblayer_private; +@@ -886,6 +887,10 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct + return rc; + } + ++ /* Truncate the key if it is too long */ ++ if (key->key.size > key_size_limit) { ++ key->key.size = key_size_limit; ++ } + if (NULL != txn) { + db_txn = txn->back_txn_txn; + } else { +@@ -930,7 +935,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct + if (txn && txn->back_special_handling_fn) { + rc = txn->back_special_handling_fn(be, BTXNACT_VLV_DEL, db, &key->key, &data, txn); + } else { +- rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, NULL); ++ rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, &data); + } + if (rc == 0) { + if (txn && txn->back_special_handling_fn) { +-- +2.52.0 + diff --git a/SOURCES/0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch b/SOURCES/0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch new file mode 100644 index 0000000..8581df5 --- /dev/null +++ b/SOURCES/0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch @@ -0,0 +1,43 @@ +From 70d323d4d9ca4d1d682ab4a273178946922c6929 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Fri, 24 Jan 2025 11:10:45 +0100 +Subject: [PATCH] Issue 6542 - RPM build errors on Fedora 42 + +Bug Description: +Fedora 42 has unified `/bin` and `/sbin`: https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin +https://docs.fedoraproject.org/en-US/packaging-guidelines/#_merged_file_system_layout + +This change causes RPM build to fail with: +``` +RPM build errors: + File not found: /builddir/build/BUILD/389-ds-base-3.1.2-build/BUILDROOT/usr/bin/openldap_to_ds +``` + +Fix Description: +Patch `setup.py.in` based on Fedora or RHEL version that support unified +`/bin` and `/sbin`. + +Fixes: https://github.com/389ds/389-ds-base/issues/6542 + +Reviewed by: @mreynolds389, @droideck (Thanks!) +--- + rpm/389-ds-base.spec.in | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in +index 258d94698..44a158ce5 100644 +--- a/rpm/389-ds-base.spec.in ++++ b/rpm/389-ds-base.spec.in +@@ -522,6 +522,9 @@ autoreconf -fiv + + %if 0%{?rhel} > 7 || 0%{?fedora} + # lib389 ++%if 0%{?fedora} >= 42 || 0%{?rhel} >= 11 ++ sed -i "/prefix/s@sbin@bin@g" src/lib389/setup.py.in ++%endif + make src/lib389/setup.py + pushd ./src/lib389 + %py3_build +-- +2.52.0 + diff --git a/SOURCES/0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch b/SOURCES/0010-Issue-6859-str2filter-is-not-fully-applying-matching.patch deleted file mode 100644 index 09cc802..0000000 --- a/SOURCES/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/SOURCES/0011-Issue-6476-Fix-build-failure-with-GCC-15.patch b/SOURCES/0011-Issue-6476-Fix-build-failure-with-GCC-15.patch new file mode 100644 index 0000000..e8b17e2 --- /dev/null +++ b/SOURCES/0011-Issue-6476-Fix-build-failure-with-GCC-15.patch @@ -0,0 +1,3318 @@ +From 9043425312dcf1ff7bf9a211a3d5a387d346a9fa Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Tue, 28 Jan 2025 15:49:46 -0500 +Subject: [PATCH] Issue 6476 - Fix build failure with GCC 15 + +Description: + +Most of the failures are our use of function pointer with a generic +typedef with unknown parameters (e.g. IFP) + +There are other IFP we use, but they are not triggering build failures +yet. + +Relates: https://github.com/389ds/389-ds-base/issues/6476 + +Reviewed by: tbordaz & spichugi (Thanks!!) +--- + .../suites/replication/acceptance_test.py | 4 +- + .../suites/replication/changelog_test.py | 11 +- + ldap/include/avl.h | 15 +- + ldap/libraries/libavl/avl.c | 44 ++-- + ldap/servers/plugins/acl/acllist.c | 23 +- + ldap/servers/plugins/collation/orfilter.c | 218 ++++++++--------- + ldap/servers/plugins/pwdstorage/md5c.c | 87 +++---- + ldap/servers/plugins/roles/roles_cache.c | 37 +-- + ldap/servers/plugins/syntaxes/bin.c | 3 +- + ldap/servers/plugins/syntaxes/syntax.h | 21 +- + ldap/servers/plugins/syntaxes/syntax_common.c | 2 +- + ldap/servers/slapd/attrsyntax.c | 23 +- + .../slapd/back-ldbm/db-bdb/bdb_import.c | 8 +- + .../slapd/back-ldbm/db-mdb/mdb_import.c | 21 +- + .../back-ldbm/db-mdb/mdb_import_threads.c | 14 +- + .../slapd/back-ldbm/db-mdb/mdb_instance.c | 6 +- + ldap/servers/slapd/back-ldbm/filterindex.c | 2 +- + ldap/servers/slapd/back-ldbm/ldbm_attr.c | 28 ++- + ldap/servers/slapd/back-ldbm/matchrule.c | 6 +- + ldap/servers/slapd/back-ldbm/misc.c | 2 +- + .../servers/slapd/back-ldbm/proto-back-ldbm.h | 2 +- + ldap/servers/slapd/back-ldbm/vlv.c | 4 +- + ldap/servers/slapd/backend.c | 55 ++--- + ldap/servers/slapd/dse.c | 16 +- + ldap/servers/slapd/entry.c | 4 +- + ldap/servers/slapd/filtercmp.c | 4 +- + ldap/servers/slapd/getfilelist.c | 8 +- + ldap/servers/slapd/pblock.c | 219 +++++++++--------- + ldap/servers/slapd/pblock_v3.h | 2 +- + ldap/servers/slapd/plugin.c | 10 +- + ldap/servers/slapd/plugin_mmr.c | 20 +- + ldap/servers/slapd/plugin_mr.c | 13 +- + ldap/servers/slapd/plugin_syntax.c | 16 +- + ldap/servers/slapd/pw.c | 4 +- + ldap/servers/slapd/slap.h | 157 ++++++------- + ldap/servers/slapd/slapi-private.h | 2 +- + ldap/servers/slapd/task.c | 2 +- + 37 files changed, 558 insertions(+), 555 deletions(-) + +diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py +index 547aee995..af3d4eaae 100644 +--- a/dirsrvtests/tests/suites/replication/acceptance_test.py ++++ b/dirsrvtests/tests/suites/replication/acceptance_test.py +@@ -597,7 +597,7 @@ def test_double_delete(topo_m4, create_entry): + time.sleep(5) + else: + time.sleep(1) +- ++ + log.info('Make searches to check if server is alive') + entries = get_repl_entries(topo_m4, TEST_ENTRY_NAME, ["uid"]) + assert not entries, "Entry deletion {} wasn't replicated successfully".format(TEST_ENTRY_DN) +@@ -688,7 +688,7 @@ def test_invalid_agmt(topo_m4): + assert False + + +-def test_warining_for_invalid_replica(topo_m4): ++def test_warning_for_invalid_replica(topo_m4): + """Testing logs to indicate the inconsistency when configuration is performed. + + :id: dd689d03-69b8-4bf9-a06e-2acd19d5e2c8 +diff --git a/dirsrvtests/tests/suites/replication/changelog_test.py b/dirsrvtests/tests/suites/replication/changelog_test.py +index b12521789..b74650a69 100644 +--- a/dirsrvtests/tests/suites/replication/changelog_test.py ++++ b/dirsrvtests/tests/suites/replication/changelog_test.py +@@ -14,6 +14,7 @@ import pytest + import time + import subprocess + import glob ++import re + from lib389.properties import TASK_WAIT + from lib389.replica import Replicas + from lib389.idm.user import UserAccounts +@@ -46,10 +47,11 @@ else: + logging.getLogger(__name__).setLevel(logging.INFO) + log = logging.getLogger(__name__) + ++ + def _check_repl_changelog_backup(instance, backup_dir): + # Note: there is no way to check dbi on lmdb backup + # That said dbscan may perhaps do it ... +- if instance.get_db_lib() is 'bdb': ++ if instance.get_db_lib() == 'bdb': + if ds_supports_new_changelog(): + backup_checkdir = os.path.join(backup_dir, DEFAULT_BENAME, BDB_CL_FILENAME) + else: +@@ -60,6 +62,7 @@ def _check_repl_changelog_backup(instance, backup_dir): + log.fatal('test_changelog5: backup directory does not exist : {}*'.format(backup_checkdir)) + assert False + ++ + def _perform_ldap_operations(topo): + """Add a test user, modify description, modrdn user and delete it""" + +@@ -760,7 +763,7 @@ def test_changelog_pagesize(topo): + 3. Should not have any 4K page size in db_stat output + """ + +- s1=topo.ms["supplier1"] ++ s1 = topo.ms["supplier1"] + fs_pagesize = os.statvfs(s1.ds_paths.db_home_dir).f_bsize + if fs_pagesize != 4096: + pytest.skip("This test requires that database filesystem prefered block size is 4K.") +@@ -770,8 +773,8 @@ def test_changelog_pagesize(topo): + log.debug(f"DEBUG: Running {cmd}") + output = subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: +- self.log.error(f'Failed to gather db statistics {cmd}: "{e.output.decode()}') +- self.log.error(e) ++ log.error(f'Failed to gather db statistics {cmd}: "{e.output.decode()}') ++ log.error(e) + raise e + assert not re.match("^4096 *Page size", output, flags=re.MULTILINE) + +diff --git a/ldap/include/avl.h b/ldap/include/avl.h +index cafe810ac..57086de23 100644 +--- a/ldap/include/avl.h ++++ b/ldap/include/avl.h +@@ -64,14 +64,15 @@ typedef int (*IFP)(); /* takes undefined arguments */ + /* avl routines */ + #define avl_getone(x) (x == 0 ? 0 : (x)->avl_data) + #define avl_onenode(x) (x == 0 || ((x)->avl_left == 0 && (x)->avl_right == 0)) +-extern int avl_insert(Avlnode **root, void *data, IFP fcmp, IFP fdup); +-extern caddr_t avl_delete(Avlnode **root, void *data, IFP fcmp); +-extern caddr_t avl_find(Avlnode *root, void *data, IFP fcmp); ++ ++extern int avl_insert(Avlnode **root, caddr_t data, int32_t(*fcmp)(caddr_t, caddr_t), int32_t(*fdup)(caddr_t, caddr_t)); ++extern caddr_t avl_delete(Avlnode **root, caddr_t data, int32_t(*fcmp)(caddr_t, caddr_t)); ++extern caddr_t avl_find(Avlnode *root, caddr_t data, int32_t(*fcmp)(caddr_t, caddr_t)); + extern caddr_t avl_getfirst(Avlnode *root); + extern caddr_t avl_getnext(void); +-extern int avl_dup_error(void); +-extern int avl_apply(Avlnode *root, IFP fn, void *arg, int stopflag, int type); +-extern int avl_free(Avlnode *root, IFP dfree); ++extern int avl_dup_error(caddr_t a, caddr_t b); ++extern int avl_apply(Avlnode *root, int32_t(*fn)(caddr_t, caddr_t), void *arg, int stopflag, int type); ++extern int avl_free(Avlnode *root, int32_t(*dfree)(caddr_t)); + + /* apply traversal types */ + #define AVL_PREORDER 1 +@@ -80,6 +81,6 @@ extern int avl_free(Avlnode *root, IFP dfree); + /* what apply returns if it ran out of nodes */ + #define AVL_NOMORE -6 + +-caddr_t avl_find_lin(Avlnode *root, caddr_t data, IFP fcmp); ++caddr_t avl_find_lin(Avlnode *root, caddr_t data, int32_t(*fcmp)(caddr_t, caddr_t)); + + #endif /* _AVL */ +diff --git a/ldap/libraries/libavl/avl.c b/ldap/libraries/libavl/avl.c +index 517e08098..84a50308a 100644 +--- a/ldap/libraries/libavl/avl.c ++++ b/ldap/libraries/libavl/avl.c +@@ -69,8 +69,8 @@ ravl_insert( + Avlnode **iroot, + caddr_t data, + int *taller, +- IFP fcmp, /* comparison function */ +- IFP fdup, /* function to call for duplicates */ ++ int32_t (*fcmp)(caddr_t, caddr_t), /* comparison function */ ++ int32_t (*fdup)(caddr_t, caddr_t), /* function to call for duplicates */ + int depth) + { + int rc, cmp, tallersub; +@@ -226,13 +226,13 @@ ravl_insert( + int + avl_insert( + Avlnode **root, +- void *data, +- IFP fcmp, +- IFP fdup) ++ caddr_t data, ++ int32_t (*fcmp)(caddr_t, caddr_t), ++ int32_t (*fdup)(caddr_t, caddr_t)) + { + int taller; + +- return ravl_insert(root, (caddr_t)data, &taller, fcmp, fdup, 0); ++ return ravl_insert(root, data, &taller, fcmp, fdup, 0); + } + + /* +@@ -374,7 +374,7 @@ static caddr_t + ravl_delete( + Avlnode **root, + caddr_t data, +- IFP fcmp, ++ int32_t (*fcmp)(caddr_t, caddr_t), + int *shorter) + { + int shortersubtree = 0; +@@ -464,15 +464,15 @@ ravl_delete( + */ + + caddr_t +-avl_delete(Avlnode **root, void *data, IFP fcmp) ++avl_delete(Avlnode **root, caddr_t data, int32_t(*fcmp)(caddr_t, caddr_t)) + { + int shorter; + +- return ravl_delete(root, (caddr_t)data, fcmp, &shorter); ++ return ravl_delete(root, data, fcmp, &shorter); + } + + static int +-avl_inapply(Avlnode *root, IFP fn, caddr_t arg, int stopflag) ++avl_inapply(Avlnode *root, int32_t(*fn)(caddr_t, caddr_t), caddr_t arg, int stopflag) + { + if (root == 0) + return (AVL_NOMORE); +@@ -491,7 +491,7 @@ avl_inapply(Avlnode *root, IFP fn, caddr_t arg, int stopflag) + } + + static int +-avl_postapply(Avlnode *root, IFP fn, caddr_t arg, int stopflag) ++avl_postapply(Avlnode *root, int32_t(*fn)(caddr_t, caddr_t), caddr_t arg, int stopflag) + { + if (root == 0) + return (AVL_NOMORE); +@@ -508,7 +508,7 @@ avl_postapply(Avlnode *root, IFP fn, caddr_t arg, int stopflag) + } + + static int +-avl_preapply(Avlnode *root, IFP fn, caddr_t arg, int stopflag) ++avl_preapply(Avlnode *root, int32_t(*fn)(caddr_t, caddr_t), caddr_t arg, int stopflag) + { + if (root == 0) + return (AVL_NOMORE); +@@ -537,7 +537,7 @@ avl_preapply(Avlnode *root, IFP fn, caddr_t arg, int stopflag) + int + avl_apply( + Avlnode *root, +- IFP fn, ++ int32_t (*fn)(caddr_t, caddr_t), + void *arg, + int stopflag, + int type) +@@ -572,9 +572,9 @@ int + avl_prefixapply( + Avlnode *root, + caddr_t data, +- IFP fmatch, ++ int32_t (*fmatch)(caddr_t, caddr_t), + caddr_t marg, +- IFP fcmp, ++ int32_t (*fcmp)(caddr_t, caddr_t, caddr_t), + caddr_t carg, + int stopflag) + { +@@ -619,7 +619,7 @@ avl_prefixapply( + */ + + int +-avl_free(Avlnode *root, IFP dfree) ++avl_free(Avlnode *root, int32_t (*dfree)(caddr_t)) + { + int nleft, nright; + +@@ -649,11 +649,11 @@ avl_free(Avlnode *root, IFP dfree) + */ + + caddr_t +-avl_find(Avlnode *root, void *data, IFP fcmp) ++avl_find(Avlnode *root, caddr_t data, int32_t (*fcmp)(caddr_t, caddr_t)) + { + int cmp; + +- while (root != 0 && (cmp = (*fcmp)((caddr_t)data, root->avl_data)) != 0) { ++ while (root != 0 && (cmp = (*fcmp)(data, root->avl_data)) != 0) { + if (cmp < 0) + root = root->avl_left; + else +@@ -671,7 +671,7 @@ avl_find(Avlnode *root, void *data, IFP fcmp) + */ + + caddr_t +-avl_find_lin(Avlnode *root, caddr_t data, IFP fcmp) ++avl_find_lin(Avlnode *root, caddr_t data, int32_t (*fcmp)(caddr_t, caddr_t)) + { + caddr_t res; + +@@ -699,7 +699,7 @@ static int avl_nextlist = 0; + + /* ARGSUSED */ + static int +-avl_buildlist(caddr_t data, int arg __attribute__((unused))) ++avl_buildlist(caddr_t data, caddr_t arg __attribute__((unused))) + { + static int slots = 0; + +@@ -766,8 +766,10 @@ avl_getnext(void) + return (avl_list[avl_nextlist++]); + } + ++/* This is always called from avl_insert, where the dup function expects two ++ * caddr_t paramters */ + int +-avl_dup_error(void) ++avl_dup_error(caddr_t a __attribute__((unused)), caddr_t b __attribute__((unused))) + { + return (-1); + } +diff --git a/ldap/servers/plugins/acl/acllist.c b/ldap/servers/plugins/acl/acllist.c +index e80c567c3..bf1168691 100644 +--- a/ldap/servers/plugins/acl/acllist.c ++++ b/ldap/servers/plugins/acl/acllist.c +@@ -233,14 +233,14 @@ __acllist_add_aci(aci_t *aci) + slapi_sdn_set_ndn_byval(aciListHead->acic_sdn, slapi_sdn_get_ndn(aci->aci_sdn)); + + /* insert the aci */ +- switch (avl_insert(&acllistRoot, aciListHead, __acllist_aciContainer_node_cmp, ++ switch (avl_insert(&acllistRoot, (caddr_t)aciListHead, __acllist_aciContainer_node_cmp, + __acllist_aciContainer_node_dup)) { + + case 1: /* duplicate ACL on the same entry */ + + /* Find the node that contains the acl. */ +- if (NULL == (head = (AciContainer *)avl_find(acllistRoot, aciListHead, +- (IFP)__acllist_aciContainer_node_cmp))) { ++ if (NULL == (head = (AciContainer *)avl_find(acllistRoot, (caddr_t)aciListHead, ++ __acllist_aciContainer_node_cmp))) { + slapi_log_err(SLAPI_PLUGIN_ACL, plugin_name, + "__acllist_add_aci - Can't insert the acl in the tree\n"); + rv = 1; +@@ -356,8 +356,8 @@ acllist_remove_aci_needsLock(const Slapi_DN *sdn, const struct berval *attr) + slapi_sdn_set_ndn_byval(aciListHead->acic_sdn, slapi_sdn_get_ndn(sdn)); + + /* now find it */ +- if (NULL == (root = (AciContainer *)avl_find(acllistRoot, aciListHead, +- (IFP)__acllist_aciContainer_node_cmp))) { ++ if (NULL == (root = (AciContainer *)avl_find(acllistRoot, (caddr_t)aciListHead, ++ __acllist_aciContainer_node_cmp))) { + /* In that case we don't have any acl for this entry. cool !!! */ + + acllist_free_aciContainer(&aciListHead); +@@ -389,7 +389,7 @@ acllist_remove_aci_needsLock(const Slapi_DN *sdn, const struct berval *attr) + slapi_log_err(SLAPI_LOG_ACL, plugin_name, + "acllist_remove_aci_needsLock - Removing container[%d]=%s\n", root->acic_index, + slapi_sdn_get_ndn(root->acic_sdn)); +- dContainer = (AciContainer *)avl_delete(&acllistRoot, aciListHead, ++ dContainer = (AciContainer *)avl_delete(&acllistRoot, (caddr_t)aciListHead, + __acllist_aciContainer_node_cmp); + acllist_free_aciContainer(&dContainer); + +@@ -472,8 +472,9 @@ acllist_done_aciContainer(AciContainer *head) + } + + static int +-free_aci_avl_container(AciContainer *data) ++free_aci_avl_container(caddr_t d) + { ++ AciContainer *data = (AciContainer *)d; + aci_t *head, *next = NULL; + + head = data->acic_list; +@@ -658,7 +659,7 @@ acllist_init_scan(Slapi_PBlock *pb, int scope __attribute__((unused)), const cha + + root = (AciContainer *)avl_find(acllistRoot, + (caddr_t)aclpb->aclpb_aclContainer, +- (IFP)__acllist_aciContainer_node_cmp); ++ __acllist_aciContainer_node_cmp); + if (index >= aclpb_max_selected_acls - 2) { + aclpb->aclpb_handles_index[0] = -1; + slapi_ch_free_string(&basedn); +@@ -750,7 +751,7 @@ acllist_aciscan_update_scan(Acl_PBlock *aclpb, char *edn) + + root = (AciContainer *)avl_find(acllistRoot, + (caddr_t)aclpb->aclpb_aclContainer, +- (IFP)__acllist_aciContainer_node_cmp); ++ __acllist_aciContainer_node_cmp); + + slapi_log_err(SLAPI_LOG_ACL, plugin_name, + "acllist_aciscan_update_scan - Searching AVL tree for update:%s: container:%d\n", +@@ -910,8 +911,8 @@ acllist_moddn_aci_needsLock(Slapi_DN *oldsdn, char *newdn) + slapi_sdn_free(&aciListHead->acic_sdn); + aciListHead->acic_sdn = oldsdn; + +- if (NULL == (head = (AciContainer *)avl_find(acllistRoot, aciListHead, +- (IFP)__acllist_aciContainer_node_cmp))) { ++ if (NULL == (head = (AciContainer *)avl_find(acllistRoot, (caddr_t)aciListHead, ++ __acllist_aciContainer_node_cmp))) { + + slapi_log_err(SLAPI_PLUGIN_ACL, plugin_name, + "acllist_moddn_aci_needsLock - Can't find the acl in the tree for moddn operation:olddn%s\n", +diff --git a/ldap/servers/plugins/collation/orfilter.c b/ldap/servers/plugins/collation/orfilter.c +index 1ed17c097..22fa7607d 100644 +--- a/ldap/servers/plugins/collation/orfilter.c ++++ b/ldap/servers/plugins/collation/orfilter.c +@@ -54,7 +54,7 @@ typedef struct or_filter_t + static or_filter_t * + or_filter_get(Slapi_PBlock *pb) + { +- auto void *obj = NULL; ++ void *obj = NULL; + if (!slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &obj)) { + return (or_filter_t *)obj; + } +@@ -64,7 +64,7 @@ or_filter_get(Slapi_PBlock *pb) + static int + or_filter_destroy(Slapi_PBlock *pb) + { +- auto or_filter_t * or = or_filter_get(pb); ++ or_filter_t * or = or_filter_get(pb); + slapi_log_err(SLAPI_LOG_FILTER, COLLATE_PLUGIN_SUBSYSTEM, + "or_filter_destroy - (%p)\n", (void *) or); + if (or != NULL) { +@@ -105,10 +105,10 @@ ss_match(struct berval *value, + * -1 nothing in value will match; give up + */ + { +- auto struct berval *vals[2]; +- auto struct berval val; +- auto struct berval key; +- auto size_t attempts = MAX_CHAR_COMBINING; ++ struct berval *vals[2]; ++ struct berval val; ++ struct berval key; ++ size_t attempts = MAX_CHAR_COMBINING; + + vals[0] = &val; + vals[1] = NULL; +@@ -117,9 +117,9 @@ ss_match(struct berval *value, + key.bv_val = key0->bv_val; + key.bv_len = key0->bv_len - 1; + while (1) { +- auto struct berval **vkeys = ix->ix_index(ix, vals, NULL); ++ struct berval **vkeys = ix->ix_index(ix, vals, NULL); + if (vkeys && vkeys[0]) { +- auto const struct berval *vkey = vkeys[0]; ++ const struct berval *vkey = vkeys[0]; + if (vkey->bv_len > key.bv_len) { + if (--attempts <= 0) { + break; /* No match at this starting point */ +@@ -138,7 +138,7 @@ ss_match(struct berval *value, + val.bv_len += LDAP_UTF8LEN(val.bv_val + val.bv_len); + } + if (value->bv_len > 0) { +- auto size_t one = LDAP_UTF8LEN(value->bv_val); ++ size_t one = LDAP_UTF8LEN(value->bv_val); + value->bv_len -= one; + value->bv_val += one; + return 1; +@@ -153,12 +153,12 @@ ss_filter_match(or_filter_t * or, struct berval **vals) + * >0 an LDAP error code + */ + { +- auto int rc = -1; /* no match */ +- auto indexer_t *ix = or->or_indexer; ++ int rc = -1; /* no match */ ++ indexer_t *ix = or->or_indexer; + if (vals != NULL) + for (; *vals; ++vals) { +- auto struct berval v; +- auto struct berval **k = or->or_match_keys; ++ struct berval v; ++ struct berval **k = or->or_match_keys; + if (k == NULL || *k == NULL) { + rc = 0; /* present */ + break; +@@ -180,12 +180,12 @@ ss_filter_match(or_filter_t * or, struct berval **vals) + break; + } + } else { /* final */ +- auto size_t attempts = MAX_CHAR_COMBINING; +- auto char *limit = v.bv_val; +- auto char *end; +- auto struct berval **vkeys; +- auto struct berval *final_vals[2]; +- auto struct berval key; ++ size_t attempts = MAX_CHAR_COMBINING; ++ char *limit = v.bv_val; ++ char *end; ++ struct berval **vkeys; ++ struct berval *final_vals[2]; ++ struct berval key; + + rc = -1; + final_vals[0] = &v; +@@ -211,7 +211,7 @@ ss_filter_match(or_filter_t * or, struct berval **vals) + v.bv_len = end - v.bv_val + 1; + vkeys = ix->ix_index(ix, final_vals, NULL); + if (vkeys && vkeys[0]) { +- auto const struct berval *vkey = vkeys[0]; ++ const struct berval *vkey = vkeys[0]; + if (vkey->bv_len > key.bv_len) { + if (--attempts <= 0) { + break; +@@ -239,11 +239,11 @@ ss_filter_match(or_filter_t * or, struct berval **vals) + static int + op_filter_match(or_filter_t * or, struct berval **vals) + { +- auto indexer_t *ix = or->or_indexer; +- auto struct berval **v = ix->ix_index(ix, vals, NULL); ++ indexer_t *ix = or->or_indexer; ++ struct berval **v = ix->ix_index(ix, vals, NULL); + if (v != NULL) + for (; *v; ++v) { +- auto struct berval **k = or->or_match_keys; ++ struct berval **k = or->or_match_keys; + if (k != NULL) + for (; *k; ++k) { + switch (or->or_op) { +@@ -282,11 +282,11 @@ or_filter_match(void *obj, Slapi_Entry *entry, Slapi_Attr *attr) + * >0 an LDAP error code + */ + { +- auto int rc = -1; /* no match */ +- auto or_filter_t * or = (or_filter_t *)obj; ++ int rc = -1; /* no match */ ++ or_filter_t * or = (or_filter_t *)obj; + for (; attr != NULL; slapi_entry_next_attr(entry, attr, &attr)) { +- auto char *type = NULL; +- auto struct berval **vals = NULL; ++ char *type = NULL; ++ struct berval **vals = NULL; + + /* + * XXXmcs 1-March-2001: This code would perform better if it did not make +@@ -352,7 +352,7 @@ static struct berval * + slapi_ch_bvdup0(struct berval *val) + /* Return a copy of val, with a 0 byte following the end. */ + { +- auto struct berval *result = (struct berval *) ++ struct berval *result = (struct berval *) + slapi_ch_malloc(sizeof(struct berval)); + slapi_ber_bvcpy(result, val); + return result; +@@ -372,12 +372,12 @@ static struct berval ** + ss_filter_values(struct berval *pattern, int *query_op) + /* Split the pattern into its substrings and return them. */ + { +- auto struct berval **result; +- auto struct berval val; +- auto size_t n; +- auto char *s; +- auto char *p; +- auto char *plimit = pattern->bv_val + pattern->bv_len; ++ struct berval **result; ++ struct berval val; ++ size_t n; ++ char *s; ++ char *p; ++ char *plimit = pattern->bv_val + pattern->bv_len; + + /* Compute the length of the result array, and + the maximum bv_len of any of its elements. */ +@@ -389,7 +389,7 @@ ss_filter_values(struct berval *pattern, int *query_op) + case WILDCARD: + ++n; + { +- auto const size_t len = (p - s); ++ const size_t len = (p - s); + if (val.bv_len < len) + val.bv_len = len; + } +@@ -402,8 +402,8 @@ ss_filter_values(struct berval *pattern, int *query_op) + } + } + if (n == 2) { /* no wildcards in pattern */ +- auto struct berval **pvec = (struct berval **)slapi_ch_malloc(sizeof(struct berval *) * 2); +- auto struct berval *pv = slapi_ch_bvdup(pattern); ++ struct berval **pvec = (struct berval **)slapi_ch_malloc(sizeof(struct berval *) * 2); ++ struct berval *pv = slapi_ch_bvdup(pattern); + pvec[0] = pv; + pvec[1] = NULL; + ss_unescape(pv); +@@ -413,7 +413,7 @@ ss_filter_values(struct berval *pattern, int *query_op) + return NULL; /* presence */ + } + { +- auto const size_t len = (p - s); ++ const size_t len = (p - s); + if (val.bv_len < len) + val.bv_len = len; + } +@@ -449,7 +449,7 @@ ss_filter_key(indexer_t *ix, struct berval *val) + struct berval *key = (struct berval *)slapi_ch_calloc(1, sizeof(struct berval)); + if (val->bv_len > 0) { + struct berval **keys = NULL; +- auto struct berval *vals[2]; ++ struct berval *vals[2]; + vals[0] = val; + vals[1] = NULL; + keys = ix->ix_index(ix, vals, NULL); +@@ -477,10 +477,10 @@ ss_filter_keys(indexer_t *ix, struct berval **values) + an empty key definitely implies an absent value. + */ + { +- auto struct berval **keys = NULL; ++ struct berval **keys = NULL; + if (values != NULL) { +- auto size_t n; /* how many substring values */ +- auto struct berval **val; ++ size_t n; /* how many substring values */ ++ struct berval **val; + for (n = 0, val = values; *val != NULL; ++n, ++val) + ; + keys = (struct berval **)slapi_ch_malloc((n + 1) * sizeof(struct berval *)); +@@ -497,25 +497,25 @@ static int or_filter_index(Slapi_PBlock *pb); + static int + or_filter_create(Slapi_PBlock *pb) + { +- auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */ +- auto char *mrOID = NULL; +- auto char *mrTYPE = NULL; +- auto struct berval *mrVALUE = NULL; +- auto or_filter_t * or = NULL; ++ int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */ ++ char *mrOID = NULL; ++ char *mrTYPE = NULL; ++ struct berval *mrVALUE = NULL; ++ or_filter_t * or = NULL; + + if (!slapi_pblock_get(pb, SLAPI_PLUGIN_MR_OID, &mrOID) && mrOID != NULL && + !slapi_pblock_get(pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE) && mrTYPE != NULL && + !slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUE, &mrVALUE) && mrVALUE != NULL) { +- auto size_t len = mrVALUE->bv_len; +- auto indexer_t *ix = NULL; +- auto int op = SLAPI_OP_EQUAL; +- auto struct berval bv; +- auto int reusable = MRF_ANY_TYPE; ++ size_t len = mrVALUE->bv_len; ++ indexer_t *ix = NULL; ++ int op = SLAPI_OP_EQUAL; ++ struct berval bv; ++ int reusable = MRF_ANY_TYPE; + + slapi_log_err(SLAPI_LOG_FILTER, COLLATE_PLUGIN_SUBSYSTEM, + "or_filter_create - (oid %s; type %s)\n", mrOID, mrTYPE); + if (len > 1 && (ix = indexer_create(mrOID)) != NULL) { +- auto char *val = mrVALUE->bv_val; ++ char *val = mrVALUE->bv_val; + switch (val[0]) { + case '=': + break; +@@ -537,7 +537,7 @@ or_filter_create(Slapi_PBlock *pb) + bv.bv_val = (len > 0) ? val : NULL; + } else { /* mrOID does not identify an ordering rule. */ + /* Is it an ordering rule OID with a relational operator suffix? */ +- auto size_t oidlen = strlen(mrOID); ++ size_t oidlen = strlen(mrOID); + if (oidlen > 2 && mrOID[oidlen - 2] == '.') { + op = atoi(mrOID + oidlen - 1); + switch (op) { +@@ -547,7 +547,7 @@ or_filter_create(Slapi_PBlock *pb) + case SLAPI_OP_GREATER_OR_EQUAL: + case SLAPI_OP_GREATER: + case SLAPI_OP_SUBSTRING: { +- auto char *or_oid = slapi_ch_strdup(mrOID); ++ char *or_oid = slapi_ch_strdup(mrOID); + or_oid[oidlen - 2] = '\0'; + ix = indexer_create(or_oid); + if (ix != NULL) { +@@ -575,7 +575,7 @@ or_filter_create(Slapi_PBlock *pb) + or->or_values[1] = NULL; + } + { +- auto struct berval **val = or->or_values; ++ struct berval **val = or->or_values; + if (val) + for (; *val; ++val) { + slapi_log_err(SLAPI_LOG_FILTER, COLLATE_PLUGIN_SUBSYSTEM, +@@ -607,7 +607,7 @@ or_filter_create(Slapi_PBlock *pb) + static indexer_t * + op_indexer_get(Slapi_PBlock *pb) + { +- auto void *obj = NULL; ++ void *obj = NULL; + if (!slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &obj)) { + return (indexer_t *)obj; + } +@@ -617,7 +617,7 @@ op_indexer_get(Slapi_PBlock *pb) + static int + op_indexer_destroy(Slapi_PBlock *pb) + { +- auto indexer_t *ix = op_indexer_get(pb); ++ indexer_t *ix = op_indexer_get(pb); + slapi_log_err(SLAPI_LOG_FILTER, COLLATE_PLUGIN_SUBSYSTEM, + "op_indexer_destroy - (%p)\n", (void *)ix); + if (ix != NULL) { +@@ -632,8 +632,8 @@ static int + op_index_entry(Slapi_PBlock *pb) + /* Compute collation keys (when writing an entry). */ + { +- auto indexer_t *ix = op_indexer_get(pb); +- auto int rc; ++ indexer_t *ix = op_indexer_get(pb); ++ int rc; + struct berval **values; + if (ix != NULL && ix->ix_index != NULL && + !slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUES, &values) && +@@ -651,10 +651,10 @@ static int + op_index_search(Slapi_PBlock *pb) + /* Compute collation keys (when searching for entries). */ + { +- auto or_filter_t * or = or_filter_get(pb); +- auto int rc = LDAP_OPERATIONS_ERROR; ++ or_filter_t * or = or_filter_get(pb); ++ int rc = LDAP_OPERATIONS_ERROR; + if (or != NULL) { +- auto indexer_t *ix = or->or_indexer; ++ indexer_t *ix = or->or_indexer; + struct berval **values; + if (or->or_index_keys == NULL && ix != NULL && ix->ix_index != NULL && !slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUES, &values)) { + or->or_index_keys = slapi_ch_bvecdup(ix->ix_index(ix, values, NULL)); +@@ -688,7 +688,7 @@ ss_indexer_free(ss_indexer_t *ss) + static ss_indexer_t * + ss_indexer_get(Slapi_PBlock *pb) + { +- auto void *obj = NULL; ++ void *obj = NULL; + if (!slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &obj)) { + return (ss_indexer_t *)obj; + } +@@ -698,7 +698,7 @@ ss_indexer_get(Slapi_PBlock *pb) + static void + ss_indexer_destroy(Slapi_PBlock *pb) + { +- auto ss_indexer_t *ss = ss_indexer_get(pb); ++ ss_indexer_t *ss = ss_indexer_get(pb); + slapi_log_err(SLAPI_LOG_FILTER, COLLATE_PLUGIN_SUBSYSTEM, + "ss_indexer_destroy - (%p)\n", (void *)ss); + if (ss) { +@@ -722,9 +722,9 @@ static int + long_enough(struct berval *bval, size_t enough) + { + if (bval) { +- auto size_t len = 0; +- auto char *next = bval->bv_val; +- auto char *last = next + bval->bv_len; ++ size_t len = 0; ++ char *next = bval->bv_val; ++ char *last = next + bval->bv_len; + while (next < last) { + LDAP_UTF8INC(next); + if (++len >= enough) { +@@ -742,23 +742,23 @@ static int + ss_index_entry(Slapi_PBlock *pb) + /* Compute substring index keys (when writing an entry). */ + { +- auto int rc = LDAP_OPERATIONS_ERROR; +- auto size_t substringsLen = 0; ++ int rc = LDAP_OPERATIONS_ERROR; ++ size_t substringsLen = 0; + struct berval **values; +- auto ss_indexer_t *ss = ss_indexer_get(pb); +- auto indexer_t *ix = ss ? ss->ss_indexer : NULL; ++ ss_indexer_t *ss = ss_indexer_get(pb); ++ indexer_t *ix = ss ? ss->ss_indexer : NULL; + if (ix != NULL && ix->ix_index != NULL && + !slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUES, &values)) { +- auto struct berval *substrings = NULL; +- auto struct berval **prefixes = NULL; +- auto struct berval **value; ++ struct berval *substrings = NULL; ++ struct berval **prefixes = NULL; ++ struct berval **value; + for (value = values; *value != NULL; ++value) { +- auto struct berval substring; ++ struct berval substring; + substring.bv_val = (*value)->bv_val; + substring.bv_len = (*value)->bv_len; + if (long_enough(&substring, SS_INDEX_LENGTH - 1)) { +- auto struct berval *prefix = &ss_index_initial; +- auto size_t offset; ++ struct berval *prefix = &ss_index_initial; ++ size_t offset; + for (offset = 0; 1; ++offset) { + ++substringsLen; + substrings = (struct berval *) +@@ -782,9 +782,9 @@ ss_index_entry(Slapi_PBlock *pb) + } + } + if (substrings != NULL) { +- auto struct berval **vector = (struct berval **) ++ struct berval **vector = (struct berval **) + slapi_ch_malloc((substringsLen + 1) * sizeof(struct berval *)); +- auto size_t i; ++ size_t i; + for (i = 0; i < substringsLen; ++i) + vector[i] = &(substrings[i]); + vector[substringsLen] = NULL; +@@ -804,21 +804,21 @@ static int + ss_index_search(Slapi_PBlock *pb) + /* Compute substring search keys (when searching for entries). */ + { +- auto int rc = LDAP_OPERATIONS_ERROR; +- auto or_filter_t * or = or_filter_get(pb); ++ int rc = LDAP_OPERATIONS_ERROR; ++ or_filter_t * or = or_filter_get(pb); + if (or) { + if (or->or_index_keys == NULL /* not yet computed */ && + or->or_values && or->or_indexer && or->or_indexer->ix_index) { +- auto size_t substringsLen = 0; +- auto struct berval *substrings = NULL; +- auto struct berval **prefixes = NULL; +- auto struct berval **value; ++ size_t substringsLen = 0; ++ struct berval *substrings = NULL; ++ struct berval **prefixes = NULL; ++ struct berval **value; + for (value = or->or_values; *value != NULL; ++value) { +- auto size_t offset; +- auto struct berval substring; ++ size_t offset; ++ struct berval substring; + substring.bv_val = (*value)->bv_val; + for (offset = 0; 1; ++offset, LDAP_UTF8INC(substring.bv_val)) { +- auto struct berval *prefix = NULL; ++ struct berval *prefix = NULL; + substring.bv_len = (*value)->bv_len - (substring.bv_val - (*value)->bv_val); + if (offset == 0 && value == or->or_values) { + if (long_enough(&substring, SS_INDEX_LENGTH - 1)) { +@@ -845,10 +845,10 @@ ss_index_search(Slapi_PBlock *pb) + } + } + if (substrings != NULL) { +- auto indexer_t *ix = or->or_indexer; +- auto struct berval **vector = (struct berval **) ++ indexer_t *ix = or->or_indexer; ++ struct berval **vector = (struct berval **) + slapi_ch_malloc((substringsLen + 1) * sizeof(struct berval *)); +- auto size_t i; ++ size_t i; + for (i = 0; i < substringsLen; ++i) + vector[i] = &(substrings[i]); + vector[substringsLen] = NULL; +@@ -872,10 +872,10 @@ static int + ss_indexable(struct berval **values) + /* at least one of the values is long enough to index */ + { +- auto struct berval **val = values; ++ struct berval **val = values; + if (val) + for (; *val; ++val) { +- auto struct berval value; ++ struct berval value; + value.bv_val = (*val)->bv_val; + value.bv_len = (*val)->bv_len; + if (val == values) { /* initial */ +@@ -899,12 +899,12 @@ static int + or_filter_index(Slapi_PBlock *pb) + /* Return an indexer and values that accelerate the given filter. */ + { +- auto or_filter_t * or = or_filter_get(pb); +- auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; +- auto IFP mrINDEX_FN = NULL; +- auto struct berval **mrVALUES = NULL; +- auto char *mrOID = NULL; +- auto int mrQUERY_OPERATOR; ++ or_filter_t * or = or_filter_get(pb); ++ int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; ++ int32_t (*mrINDEX_FN)(Slapi_PBlock *) = NULL; ++ struct berval **mrVALUES = NULL; ++ char *mrOID = NULL; ++ int mrQUERY_OPERATOR; + if (or && or->or_indexer && or->or_indexer->ix_index) { + switch (or->or_op) { + case SLAPI_OP_LESS: +@@ -920,7 +920,7 @@ or_filter_index(Slapi_PBlock *pb) + case SLAPI_OP_SUBSTRING: + if (ss_indexable(or->or_values)) { + if (or->or_oid == NULL) { +- auto const size_t len = strlen(or->or_indexer->ix_oid); ++ const size_t len = strlen(or->or_indexer->ix_oid); + or->or_oid = slapi_ch_malloc(len + 3); + memcpy(or->or_oid, or->or_indexer->ix_oid, len); + sprintf(or->or_oid + len, ".%1i", SLAPI_OP_SUBSTRING); +@@ -952,15 +952,15 @@ or_filter_index(Slapi_PBlock *pb) + static int + or_indexer_create(Slapi_PBlock *pb) + { +- auto int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */ +- auto char *mrOID = NULL; +- auto void *mrOBJECT = NULL; ++ int rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* failed to initialize */ ++ char *mrOID = NULL; ++ void *mrOBJECT = NULL; + if (slapi_pblock_get(pb, SLAPI_PLUGIN_MR_OID, &mrOID) || mrOID == NULL) { + slapi_log_err(SLAPI_LOG_FILTER, COLLATE_PLUGIN_SUBSYSTEM, + "or_indexer_create - No OID parameter\n"); + } else { +- auto indexer_t *ix = indexer_create(mrOID); +- auto char *mrTYPE = NULL; ++ indexer_t *ix = indexer_create(mrOID); ++ char *mrTYPE = NULL; + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE); + slapi_log_err(SLAPI_LOG_FILTER, "or_indexer_create", "(oid %s; type %s)\n", + mrOID, mrTYPE ? mrTYPE : ""); +@@ -977,14 +977,14 @@ or_indexer_create(Slapi_PBlock *pb) + } + } else { /* mrOID does not identify an ordering rule. */ + /* Is it an ordering rule OID with the substring suffix? */ +- auto size_t oidlen = strlen(mrOID); ++ size_t oidlen = strlen(mrOID); + if (oidlen > 2 && mrOID[oidlen - 2] == '.' && + atoi(mrOID + oidlen - 1) == SLAPI_OP_SUBSTRING) { +- auto char *or_oid = slapi_ch_strdup(mrOID); ++ char *or_oid = slapi_ch_strdup(mrOID); + or_oid[oidlen - 2] = '\0'; + ix = indexer_create(or_oid); + if (ix != NULL) { +- auto ss_indexer_t *ss = (ss_indexer_t *)slapi_ch_malloc(sizeof(ss_indexer_t)); ++ ss_indexer_t *ss = (ss_indexer_t *)slapi_ch_malloc(sizeof(ss_indexer_t)); + ss->ss_indexer = ix; + oidlen = strlen(ix->ix_oid); + ss->ss_oid = slapi_ch_malloc(oidlen + 3); +diff --git a/ldap/servers/plugins/pwdstorage/md5c.c b/ldap/servers/plugins/pwdstorage/md5c.c +index e7085a68d..8075dcb80 100644 +--- a/ldap/servers/plugins/pwdstorage/md5c.c ++++ b/ldap/servers/plugins/pwdstorage/md5c.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) 2005-2025 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -69,20 +69,17 @@ static unsigned char PADDING[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +-/* F, G, H and I are basic MD5 functions. +- */ ++/* F, G, H and I are basic MD5 functions. */ + #define F(x, y, z) (((x) & (y)) | ((~(x)) & (z))) + #define G(x, y, z) (((x) & (z)) | ((y) & (~(z)))) + #define H(x, y, z) ((x) ^ (y) ^ (z)) + #define I(x, y, z) ((y) ^ ((x) | (~(z)))) + +-/* ROTATE_LEFT rotates x left n bits. +- */ ++/* ROTATE_LEFT rotates x left n bits. */ + #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +-Rotation is separate from addition to prevent recomputation. +- */ ++ * Rotation is separate from addition to prevent recomputation. */ + #define FF(a, b, c, d, x, s, ac) \ + { \ + (a) += F((b), (c), (d)) + (x) + (UINT4)(ac); \ +@@ -108,14 +105,11 @@ Rotation is separate from addition to prevent recomputation. + (a) += (b); \ + } + +-/* MD5 initialization. Begins an MD5 operation, writing a new context. +- */ +-void mta_MD5Init(context) +- mta_MD5_CTX *context; /* context */ ++/* MD5 initialization. Begins an MD5 operation, writing a new context. */ ++void mta_MD5Init(mta_MD5_CTX *context) + { + context->count[0] = context->count[1] = 0; +- /* Load magic initialization constants. +-*/ ++ /* Load magic initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; +@@ -123,13 +117,9 @@ void mta_MD5Init(context) + } + + /* MD5 block update operation. Continues an MD5 message-digest +- operation, processing another message block, and updating the +- context. +- */ +-void mta_MD5Update(context, input, inputLen) +- mta_MD5_CTX *context; /* context */ +-const unsigned char *input; /* input block */ +-unsigned int inputLen; /* length of input block */ ++ * operation, processing another message block, and updating the ++ * context. */ ++void mta_MD5Update(mta_MD5_CTX *context, const unsigned char *input, unsigned int inputLen) + { + unsigned int i, index, partLen; + +@@ -143,8 +133,7 @@ unsigned int inputLen; /* length of input block */ + + partLen = 64 - index; + +- /* Transform as many times as possible. +-*/ ++ /* Transform as many times as possible. */ + if (inputLen >= partLen) { + MD5_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform(context->state, context->buffer); +@@ -162,10 +151,8 @@ unsigned int inputLen; /* length of input block */ + } + + /* MD5 finalization. Ends an MD5 message-digest operation, writing the +- the message digest and zeroizing the context. +- */ +-void mta_MD5Final(digest, context) unsigned char digest[16]; /* message digest */ +-mta_MD5_CTX *context; /* context */ ++ * the message digest and zeroizing the context. */ ++void mta_MD5Final(unsigned char digest[16], mta_MD5_CTX *context) + { + unsigned char bits[8]; + unsigned int index, padLen; +@@ -173,8 +160,7 @@ mta_MD5_CTX *context; /* context */ + /* Save number of bits */ + Encode(bits, context->count, 8); + +- /* Pad out to 56 mod 64. +-*/ ++ /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + mta_MD5Update(context, PADDING, padLen); +@@ -185,16 +171,12 @@ mta_MD5_CTX *context; /* context */ + /* Store state in digest */ + Encode(digest, context->state, 16); + +- /* Zeroize sensitive information. +-*/ ++ /* Zeroize sensitive information.*/ + MD5_memset((POINTER)context, 0, sizeof(*context)); + } + +-/* MD5 basic transformation. Transforms state based on block. +- */ +-static void MD5Transform(state, block) +- UINT4 state[4]; +-const unsigned char block[64]; ++/* MD5 basic transformation. Transforms state based on block. */ ++static void MD5Transform(UINT4 state[4], const unsigned char block[64]) + { + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + +@@ -277,17 +259,13 @@ const unsigned char block[64]; + state[2] += c; + state[3] += d; + +- /* Zeroize sensitive information. +-*/ ++ /* Zeroize sensitive information. */ + MD5_memset((POINTER)x, 0, sizeof(x)); + } + + /* Encodes input (UINT4) into output (unsigned char). Assumes len is +- a multiple of 4. +- */ +-static void Encode(output, input, len) unsigned char *output; +-const UINT4 *input; +-unsigned int len; ++ * a multiple of 4. */ ++static void Encode(unsigned char *output, const UINT4 *input, unsigned int len) + { + unsigned int i, j; + +@@ -300,12 +278,8 @@ unsigned int len; + } + + /* Decodes input (unsigned char) into output (UINT4). Assumes len is +- a multiple of 4. +- */ +-static void Decode(output, input, len) +- UINT4 *output; +-const unsigned char *input; +-unsigned int len; ++ * a multiple of 4. */ ++static void Decode(UINT4 *output, const unsigned char *input, unsigned int len) + { + unsigned int i, j; + +@@ -314,13 +288,8 @@ unsigned int len; + (((UINT4)input[j + 2]) << 16) | (((UINT4)input[j + 3]) << 24); + } + +-/* Note: Replace "for loop" with standard memcpy if possible. +- */ +- +-static void MD5_memcpy(output, input, len) +- POINTER output; +-const POINTER input; +-unsigned int len; ++/* Note: Replace "for loop" with standard memcpy if possible. */ ++static void MD5_memcpy(POINTER output, const POINTER input, unsigned int len) + { + unsigned int i; + +@@ -328,12 +297,8 @@ unsigned int len; + output[i] = input[i]; + } + +-/* Note: Replace "for loop" with standard memset if possible. +- */ +-static void MD5_memset(output, value, len) +- POINTER output; +-int value; +-unsigned int len; ++/* Note: Replace "for loop" with standard memset if possible. */ ++static void MD5_memset(POINTER output, int value, unsigned int len) + { + unsigned int i; + +diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c +index 60f5a919a..9fadcdfc4 100644 +--- a/ldap/servers/plugins/roles/roles_cache.c ++++ b/ldap/servers/plugins/roles/roles_cache.c +@@ -156,8 +156,8 @@ static int roles_is_inscope(Slapi_Entry *entry_to_check, role_object *this_role) + static void berval_set_string(struct berval *bv, const char *string); + static void roles_cache_role_def_delete(roles_cache_def *role_def); + static void roles_cache_role_def_free(roles_cache_def *role_def); +-static void roles_cache_role_object_free(role_object *this_role); +-static int roles_cache_role_object_nested_free(role_object_nested *this_role); ++static int roles_cache_role_object_free(caddr_t this_role); ++static int roles_cache_role_object_nested_free(caddr_t this_role); + static int roles_cache_dump(caddr_t data, caddr_t arg); + static int roles_cache_add_entry_cb(Slapi_Entry *e, void *callback_data); + static void roles_cache_result_cb(int rc, void *callback_data); +@@ -578,11 +578,11 @@ roles_cache_update(roles_cache_def *suffix_to_update) + if ((operation == SLAPI_OPERATION_MODIFY) || + (operation == SLAPI_OPERATION_DELETE)) { + +- to_delete = (role_object *)avl_delete(&(suffix_to_update->avl_tree), dn, roles_cache_find_node); +- roles_cache_role_object_free(to_delete); ++ to_delete = (role_object *)avl_delete(&(suffix_to_update->avl_tree), (caddr_t)dn, roles_cache_find_node); ++ roles_cache_role_object_free((caddr_t)to_delete); + to_delete = NULL; + if (slapi_is_loglevel_set(SLAPI_LOG_PLUGIN)) { +- avl_apply(suffix_to_update->avl_tree, (IFP)roles_cache_dump, &rc, -1, AVL_INORDER); ++ avl_apply(suffix_to_update->avl_tree, roles_cache_dump, &rc, -1, AVL_INORDER); + } + } + if ((operation == SLAPI_OPERATION_MODIFY) || +@@ -1513,7 +1513,7 @@ roles_cache_listroles_ext(vattr_context *c, Slapi_Entry *entry, int return_value + /* XXX really need a mutex for this read operation ? */ + slapi_rwlock_rdlock(roles_cache->cache_lock); + +- avl_apply(roles_cache->avl_tree, (IFP)roles_cache_build_nsrole, &arg, -1, AVL_INORDER); ++ avl_apply(roles_cache->avl_tree, roles_cache_build_nsrole, &arg, -1, AVL_INORDER); + + slapi_rwlock_unlock(roles_cache->cache_lock); + +@@ -1630,7 +1630,7 @@ roles_check(Slapi_Entry *entry_to_check, Slapi_DN *role_dn, int *present) + } + slapi_rwlock_unlock(global_lock); + +- this_role = (role_object *)avl_find(roles_cache->avl_tree, role_dn, (IFP)roles_cache_find_node); ++ this_role = (role_object *)avl_find(roles_cache->avl_tree, (caddr_t)role_dn, roles_cache_find_node); + + /* MAB: For some reason the assumption made by this function (the role exists and is in scope) + * does not seem to be true... this_role might be NULL after the avl_find call (is the avl_tree +@@ -1768,7 +1768,7 @@ roles_is_entry_member_of_object_ext(vattr_context *c, caddr_t data, caddr_t argu + case ROLE_TYPE_NESTED: { + /* Go through the tree of the nested DNs */ + get_nsrole->hint++; +- avl_apply(this_role->avl_tree, (IFP)roles_check_nested, get_nsrole, 0, AVL_INORDER); ++ avl_apply(this_role->avl_tree, roles_check_nested, get_nsrole, 0, AVL_INORDER); + get_nsrole->hint--; + + /* kexcoff?? */ +@@ -1904,12 +1904,12 @@ roles_check_nested(caddr_t data, caddr_t arg) + } + + if (slapi_is_loglevel_set(SLAPI_LOG_PLUGIN)) { +- avl_apply(roles_cache->avl_tree, (IFP)roles_cache_dump, &rc, -1, AVL_INORDER); ++ avl_apply(roles_cache->avl_tree, roles_cache_dump, &rc, -1, AVL_INORDER); + } + + this_role = (role_object *)avl_find(roles_cache->avl_tree, +- current_nested_role->dn, +- (IFP)roles_cache_find_node); ++ (caddr_t)current_nested_role->dn, ++ roles_cache_find_node); + + if (this_role == NULL) { + /* the nested role doesn't exist */ +@@ -2029,7 +2029,7 @@ roles_cache_role_def_free(roles_cache_def *role_def) + + slapi_lock_mutex(role_def->stop_lock); + +- avl_free(role_def->avl_tree, (IFP)roles_cache_role_object_free); ++ avl_free(role_def->avl_tree, roles_cache_role_object_free); + slapi_sdn_free(&(role_def->suffix_dn)); + slapi_destroy_rwlock(role_def->cache_lock); + role_def->cache_lock = NULL; +@@ -2060,14 +2060,16 @@ roles_cache_role_def_free(roles_cache_def *role_def) + /* roles_cache_role_object_free + ---------------------------- + */ +-static void +-roles_cache_role_object_free(role_object *this_role) ++static int ++roles_cache_role_object_free(caddr_t tr) + { ++ role_object *this_role = (role_object *)tr; ++ + slapi_log_err(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_object_free\n"); + + if (this_role == NULL) { +- return; ++ return 0; + } + + switch (this_role->type) { +@@ -2097,14 +2099,17 @@ roles_cache_role_object_free(role_object *this_role) + + slapi_log_err(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "<-- roles_cache_role_object_free\n"); ++ return 0; + } + + /* roles_cache_role_object_nested_free + ------------------------------------ + */ + static int +-roles_cache_role_object_nested_free(role_object_nested *this_role) ++roles_cache_role_object_nested_free(caddr_t tr) + { ++ role_object_nested *this_role = (role_object_nested *)tr; ++ + slapi_log_err(SLAPI_LOG_PLUGIN, + ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_role_object_nested_free\n"); + +diff --git a/ldap/servers/plugins/syntaxes/bin.c b/ldap/servers/plugins/syntaxes/bin.c +index ab67cecf9..f793f3539 100644 +--- a/ldap/servers/plugins/syntaxes/bin.c ++++ b/ldap/servers/plugins/syntaxes/bin.c +@@ -139,7 +139,8 @@ static struct mr_plugin_def mr_plugin_table[] = { + NULL, + bin_compare, + NULL /* mr_normalize */ +- }}; ++ } ++}; + /* + certificateExactMatch + certificateListExactMatch +diff --git a/ldap/servers/plugins/syntaxes/syntax.h b/ldap/servers/plugins/syntaxes/syntax.h +index c743532c1..7e53c3586 100644 +--- a/ldap/servers/plugins/syntaxes/syntax.h ++++ b/ldap/servers/plugins/syntaxes/syntax.h +@@ -104,21 +104,22 @@ struct mr_plugin_def + Slapi_PluginDesc mr_plg_desc; /* for SLAPI_PLUGIN_DESCRIPTION */ + const char **mr_names; /* list of oid and names, NULL terminated SLAPI_PLUGIN_MR_NAMES */ + /* these are optional for new style mr plugins */ +- IFP mr_filter_create; /* old style factory function SLAPI_PLUGIN_MR_FILTER_CREATE_FN */ +- IFP mr_indexer_create; /* old style factory function SLAPI_PLUGIN_MR_INDEXER_CREATE_FN */ ++ int32_t (*mr_filter_create)(Slapi_PBlock *); /* old style factory function SLAPI_PLUGIN_MR_FILTER_CREATE_FN */ ++ int32_t (*mr_indexer_create)(Slapi_PBlock *); /* old style factory function SLAPI_PLUGIN_MR_INDEXER_CREATE_FN */ + /* new style syntax plugin functions */ + /* not all functions will apply to all matching rule types */ + /* e.g. a SUBSTR rule will not have a filter_ava func */ +- IFP mr_filter_ava; /* SLAPI_PLUGIN_MR_FILTER_AVA */ +- IFP mr_filter_sub; /* SLAPI_PLUGIN_MR_FILTER_SUB */ +- IFP mr_values2keys; /* SLAPI_PLUGIN_MR_VALUES2KEYS */ +- IFP mr_assertion2keys_ava; /* SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA */ +- IFP mr_assertion2keys_sub; /* SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB */ +- IFP mr_compare; /* SLAPI_PLUGIN_MR_COMPARE - only for ORDERING */ +- VFPV mr_normalize; ++ int32_t (*mr_filter_ava)(Slapi_PBlock *, struct berval *, Slapi_Value **, int32_t, Slapi_Value **); /* SLAPI_PLUGIN_MR_FILTER_AVA */ ++ int32_t (*mr_filter_sub)(Slapi_PBlock *, char *, char **, char *, Slapi_Value **); /* SLAPI_PLUGIN_MR_FILTER_SUB */ ++ int32_t (*mr_values2keys)(Slapi_PBlock *, Slapi_Value **, Slapi_Value ***, int32_t); /* SLAPI_PLUGIN_MR_VALUES2KEYS */ ++ int32_t (*mr_assertion2keys_ava)(Slapi_PBlock *, Slapi_Value *, Slapi_Value ***, int32_t); ++ int32_t (*mr_assertion2keys_sub)(Slapi_PBlock *, char *, char **, char *, Slapi_Value ***); /* SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB */ ++ int32_t (*mr_compare)(struct berval *, struct berval *); /* SLAPI_PLUGIN_MR_COMPARE - only for ORDERING */ ++ void (*mr_normalize)(Slapi_PBlock *, char *, int32_t, char **); + }; + +-int syntax_register_matching_rule_plugins(struct mr_plugin_def mr_plugin_table[], size_t mr_plugin_table_size, IFP matching_rule_plugin_init); ++int syntax_register_matching_rule_plugins(struct mr_plugin_def mr_plugin_table[], size_t mr_plugin_table_size, ++ int32_t (*matching_rule_plugin_init)(Slapi_PBlock *)); + int syntax_matching_rule_plugin_init(Slapi_PBlock *pb, struct mr_plugin_def mr_plugin_table[], size_t mr_plugin_table_size); + + #endif +diff --git a/ldap/servers/plugins/syntaxes/syntax_common.c b/ldap/servers/plugins/syntaxes/syntax_common.c +index 821d4d557..7407f0b9c 100644 +--- a/ldap/servers/plugins/syntaxes/syntax_common.c ++++ b/ldap/servers/plugins/syntaxes/syntax_common.c +@@ -16,7 +16,7 @@ int + syntax_register_matching_rule_plugins( + struct mr_plugin_def mr_plugin_table[], + size_t mr_plugin_table_size, +- IFP matching_rule_plugin_init) ++ int32_t (*matching_rule_plugin_init)(Slapi_PBlock *)) + { + int rc = -1; + size_t ii; +diff --git a/ldap/servers/slapd/attrsyntax.c b/ldap/servers/slapd/attrsyntax.c +index 6bbcb05ac..e03ea6478 100644 +--- a/ldap/servers/slapd/attrsyntax.c ++++ b/ldap/servers/slapd/attrsyntax.c +@@ -608,10 +608,11 @@ attr_syntax_exists(const char *attr_name) + + static void default_dirstring_normalize_int(char *s, int trim_spaces); + +-static int +-default_dirstring_filter_ava(struct berval *bvfilter __attribute__((unused)), +- Slapi_Value **bvals __attribute__((unused)), +- int ftype __attribute__((unused)), ++static int32_t ++default_dirstring_filter_ava(Slapi_PBlock *pb __attribute__((unused)), ++ const struct berval *bv __attribute__((unused)), ++ Slapi_Value **vals __attribute__((unused)), ++ int32_t ftype __attribute__((unused)), + Slapi_Value **retVal __attribute__((unused))) + { + return (0); +@@ -621,7 +622,7 @@ static int + default_dirstring_values2keys(Slapi_PBlock *pb __attribute__((unused)), + Slapi_Value **bvals, + Slapi_Value ***ivals, +- int ftype) ++ int32_t ftype) + { + int numbvals = 0; + Slapi_Value **nbvals, **nbvlp; +@@ -664,11 +665,11 @@ default_dirstring_values2keys(Slapi_PBlock *pb __attribute__((unused)), + return (0); + } + +-static int ++static int32_t + default_dirstring_assertion2keys_ava(Slapi_PBlock *pb __attribute__((unused)), + Slapi_Value *val __attribute__((unused)), + Slapi_Value ***ivals __attribute__((unused)), +- int ftype __attribute__((unused))) ++ int32_t ftype __attribute__((unused))) + { + return (0); + } +@@ -759,11 +760,11 @@ attr_syntax_default_plugin(const char *nameoroid) + pi->plg_syntax_oid = slapi_ch_strdup(nameoroid); + + +- pi->plg_syntax_filter_ava = (IFP)default_dirstring_filter_ava; +- pi->plg_syntax_values2keys = (IFP)default_dirstring_values2keys; +- pi->plg_syntax_assertion2keys_ava = (IFP)default_dirstring_assertion2keys_ava; ++ pi->plg_syntax_filter_ava = default_dirstring_filter_ava; ++ pi->plg_syntax_values2keys = default_dirstring_values2keys; ++ pi->plg_syntax_assertion2keys_ava = default_dirstring_assertion2keys_ava; + pi->plg_syntax_compare = (IFP)default_dirstring_cmp; +- pi->plg_syntax_normalize = (VFPV)default_dirstring_normalize; ++ pi->plg_syntax_normalize = default_dirstring_normalize; + + return (pi); + } +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 2bb6b0267..23dd7983f 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c +@@ -1298,8 +1298,10 @@ bdb_update_subordinatecounts(backend *be, ImportJob *job, DB_TXN *txn) + + /* Function used to gather a list of indexed attrs */ + static int +-bdb_import_attr_callback(void *node, void *param) ++bdb_import_attr_callback(caddr_t n, caddr_t p) + { ++ void *node = (void *)n; ++ void *param = (void *)p; + ImportJob *job = (ImportJob *)param; + struct attrinfo *a = (struct attrinfo *)node; + +@@ -2215,9 +2217,9 @@ bdb_public_bdb_import_main(void *arg) + /* Here, we get an AVL tree which contains nodes for all attributes + * in the schema. Given this tree, we need to identify those nodes + * which are marked for indexing. */ +- avl_apply(job->inst->inst_attrs, (IFP)bdb_import_attr_callback, ++ avl_apply(job->inst->inst_attrs, bdb_import_attr_callback, + (caddr_t)job, -1, AVL_INORDER); +- vlv_getindices((IFP)bdb_import_attr_callback, (void *)job, be); ++ vlv_getindices(bdb_import_attr_callback, (void *)job, be); + } + + /* Determine how much index buffering space to allocate to each index */ +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 d0efc1bca..f945f5a59 100644 +--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c ++++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c +@@ -347,9 +347,11 @@ dbmdb_update_subordinatecounts(backend *be, ImportJob *job, dbi_txn_t *txn) + } + + /* Function used to gather a list of indexed attrs */ +-static int +-dbmdb_import_attr_callback(void *node, void *param) ++static int32_t ++dbmdb_import_attr_callback(caddr_t n, caddr_t p) + { ++ void *node = (void *)n; ++ void *param = (void *)p; + ImportJob *job = (ImportJob *)param; + struct attrinfo *a = (struct attrinfo *)node; + +@@ -788,11 +790,11 @@ dbmdb_import_all_done(ImportJob *job, int ret) + /* Bring backend online again: + * In lmdb case, the import framework is also used for reindexing + * while in bdb case reindexing uses its own code. +- * So dbmdb_import_all_done is called either after ++ * So dbmdb_import_all_done is called either after + * dbmdb_ldif2db or after dbmdb_db2index while + * bdb_import_all_done is only called after bdb_ldif2db. + * +- * dbmdb_db2index uses instance_set_busy_and_readonly ++ * dbmdb_db2index uses instance_set_busy_and_readonly + * while dbmdb_ldif2db uses slapi_mtn_be_disable + * and these functions have to be reverted accordingly. + */ +@@ -821,9 +823,12 @@ dbmdb_import_all_done(ImportJob *job, int ret) + + /* vlv_getindices callback that truncate vlv index (in reindex case) */ + static int +-truncate_index_dbi(struct attrinfo *ai, ImportCtx_t *ctx) ++truncate_index_dbi(caddr_t a, caddr_t c) + { ++ struct attrinfo *ai = (struct attrinfo *)a; ++ ImportCtx_t *ctx = (ImportCtx_t *)c; + int rc = 0; ++ + if (is_reindexed_attr(ai->ai_type, ctx, ctx->indexVlvs)) { + backend *be = ctx->job->inst->inst_be; + dbmdb_dbi_t *dbi = NULL; +@@ -878,9 +883,9 @@ dbmdb_public_dbmdb_import_main(void *arg) + /* Here, we get an AVL tree which contains nodes for all attributes + * in the schema. Given this tree, we need to identify those nodes + * which are marked for indexing. */ +- avl_apply(job->inst->inst_attrs, (IFP)dbmdb_import_attr_callback, ++ avl_apply(job->inst->inst_attrs, dbmdb_import_attr_callback, + (caddr_t)job, -1, AVL_INORDER); +- vlv_getindices((IFP)dbmdb_import_attr_callback, (void *)job, be); ++ vlv_getindices(dbmdb_import_attr_callback, (void *)job, be); + } + + /* insure all dbi get open */ +@@ -901,7 +906,7 @@ dbmdb_public_dbmdb_import_main(void *arg) + pthread_mutex_unlock(&job->wire_lock); + break; + case IM_INDEX: +- vlv_getindices((IFP)truncate_index_dbi, ctx, job->inst->inst_be); ++ vlv_getindices(truncate_index_dbi, ctx, job->inst->inst_be); + default: + break; + } +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 6978bf5e3..545099b5f 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 +@@ -2895,7 +2895,7 @@ look4indexinfo(ImportCtx_t *ctx, const char *attrname) + { + MdbIndexInfo_t searched_mii = {0}; + searched_mii.name = (char*) attrname; +- return (MdbIndexInfo_t *)avl_find(ctx->indexes, &searched_mii, cmp_mii); ++ return (MdbIndexInfo_t *)avl_find(ctx->indexes, (caddr_t)&searched_mii, cmp_mii); + } + + /* Prepare key and data for updating parentid or ancestorid indexes */ +@@ -3460,7 +3460,7 @@ dbmdb_add_import_index(ImportCtx_t *ctx, const char *name, IndexInfo *ii) + + DBG_LOG(DBGMDB_LEVEL_OTHER,"Calling dbmdb_open_dbi_from_filename for %s flags = 0x%x", mii->name, dbi_flags); + dbmdb_open_dbi_from_filename(&mii->dbi, job->inst->inst_be, mii->name, mii->ai, dbi_flags); +- avl_insert(&ctx->indexes, mii, cmp_mii, NULL); ++ avl_insert(&ctx->indexes, (caddr_t)mii, cmp_mii, NULL); + } + + /* +@@ -3486,7 +3486,7 @@ dbmdb_open_redirect_db(ImportCtx_t *ctx) + mii->ai = ai; + mii->flags = MII_SKIP | MII_NOATTR; + dbmdb_open_dbi_from_filename(&mii->dbi, be, mii->name, mii->ai, dbi_flags); +- avl_insert(&ctx->indexes, mii, cmp_mii, NULL); ++ avl_insert(&ctx->indexes, (caddr_t)mii, cmp_mii, NULL); + ctx->redirect = mii; + } + +@@ -3532,11 +3532,13 @@ dbmdb_build_import_index_list(ImportCtx_t *ctx) + + } + +-void +-free_ii(MdbIndexInfo_t *ii) ++static int32_t ++free_ii(caddr_t i) + { ++ MdbIndexInfo_t *ii = (MdbIndexInfo_t *)i; + slapi_ch_free_string(&ii->name); + slapi_ch_free((void**)&ii); ++ return 0; + } + + /* +@@ -4338,7 +4340,7 @@ dbmdb_free_import_ctx(ImportJob *job) + dbmdb_import_q_destroy(&ctx->bulkq); + slapi_ch_free((void**)&ctx->id2entry->name); + slapi_ch_free((void**)&ctx->id2entry); +- avl_free(ctx->indexes, (IFP) free_ii); ++ avl_free(ctx->indexes, free_ii); + ctx->indexes = NULL; + charray_free(ctx->indexAttrs); + charray_free(ctx->indexVlvs); +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 682fd70e2..145811237 100644 +--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c ++++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_instance.c +@@ -336,8 +336,10 @@ int add_dbi(dbi_open_ctx_t *octx, backend *be, const char *fname, int flags) + + /* avlapply callback to open/create the dbi needed to handle an index */ + static int +-add_index_dbi(struct attrinfo *ai, dbi_open_ctx_t *octx) ++add_index_dbi(caddr_t attr, caddr_t otx) + { ++ struct attrinfo *ai = (struct attrinfo *)attr; ++ dbi_open_ctx_t *octx = (dbi_open_ctx_t *)otx; + int flags = octx->ctx->readonly ? MDB_RDONLY: MDB_CREATE; + char *rcdbname = NULL; + +@@ -473,7 +475,7 @@ dbmdb_open_all_files(dbmdb_ctx_t *ctx, backend *be) + } + if (be->vlvSearchList_lock) { + /* vlv search list is initialized so we can use it */ +- vlv_getindices((IFP)add_index_dbi, &octx, be); ++ vlv_getindices(add_index_dbi, &octx, be); + } else if (vlv_list) { + char *rcdbname = NULL; + for (size_t i=0; rc == 0 && vlv_list[i]; i++) { +diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c +index 8bb22570b..c01a58ccd 100644 +--- a/ldap/servers/slapd/back-ldbm/filterindex.c ++++ b/ldap/servers/slapd/back-ldbm/filterindex.c +@@ -462,7 +462,7 @@ extensible_candidates( + case SLAPI_OP_EQUAL: + case SLAPI_OP_GREATER_OR_EQUAL: + case SLAPI_OP_GREATER: { +- IFP mrINDEX = NULL; ++ int32_t (*mrINDEX)(Slapi_PBlock *) = NULL; + void *mrOBJECT = NULL; + struct berval **mrVALUES = NULL; + char *mrOID = NULL; +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attr.c b/ldap/servers/slapd/back-ldbm/ldbm_attr.c +index 30bfd1349..7fe6f1405 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_attr.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_attr.c +@@ -68,7 +68,7 @@ attrinfo_delete(struct attrinfo **pp) + } + + static int +-attrinfo_internal_delete(caddr_t data, caddr_t arg __attribute__((unused))) ++attrinfo_internal_delete(caddr_t data) + { + struct attrinfo *n = (struct attrinfo *)data; + attrinfo_delete(&n); +@@ -85,16 +85,19 @@ attrinfo_deletetree(ldbm_instance *inst) + static int + ainfo_type_cmp( + char *type, +- struct attrinfo *a) ++ caddr_t val) + { ++ struct attrinfo *a = (struct attrinfo *)val; + return (strcasecmp(type, a->ai_type)); + } + + static int + ainfo_cmp( +- struct attrinfo *a, +- struct attrinfo *b) ++ caddr_t val1, ++ caddr_t val2) + { ++ struct attrinfo *a = (struct attrinfo *)val1; ++ struct attrinfo *b = (struct attrinfo *)val2; + return (strcasecmp(a->ai_type, b->ai_type)); + } + +@@ -102,7 +105,7 @@ void + attrinfo_delete_from_tree(backend *be, struct attrinfo *ai) + { + ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; +- avl_delete(&inst->inst_attrs, ai, ainfo_cmp); ++ avl_delete(&inst->inst_attrs, (caddr_t)ai, ainfo_cmp); + } + + /* +@@ -117,9 +120,12 @@ attrinfo_delete_from_tree(backend *be, struct attrinfo *ai) + + static int + ainfo_dup( +- struct attrinfo *a, +- struct attrinfo *b) ++ caddr_t val1, ++ caddr_t val2) + { ++ struct attrinfo *a = (struct attrinfo *)val1; ++ struct attrinfo *b = (struct attrinfo *)val2; ++ + /* merge duplicate indexing information */ + if (b->ai_indexmask == 0 || b->ai_indexmask == INDEX_OFFLINE) { + a->ai_indexmask = INDEX_OFFLINE; /* turns off all indexes */ +@@ -203,7 +209,7 @@ attr_index_parse_idlistsize_values(Slapi_Attr *attr, struct index_idlistsizeinfo + char *lasts = NULL; + char *val; + int syntaxcheck = config_get_syntaxcheck(); +- IFP syntax_validate_fn = syntaxcheck ? attr->a_plugin->plg_syntax_validate : NULL; ++ int32_t (*syntax_validate_fn)(struct berval *) = syntaxcheck ? attr->a_plugin->plg_syntax_validate : NULL; + char staticfiltstrbuf[1024]; /* for small filter strings */ + char *filtstrbuf = staticfiltstrbuf; /* default if not malloc'd */ + size_t filtstrbuflen = sizeof(staticfiltstrbuf); /* default if not malloc'd */ +@@ -880,7 +886,7 @@ attr_index_config( + * It would improve speed to save the indexer, for future use. + * But, for simplicity, we destroy it now: + */ +- IFP mrDESTROY = NULL; ++ int32_t (*mrDESTROY)(Slapi_PBlock *) = NULL; + if (!slapi_pblock_get(pb, SLAPI_PLUGIN_DESTROY_FN, &mrDESTROY) && + mrDESTROY != NULL) { + mrDESTROY(pb); +@@ -941,7 +947,7 @@ attr_index_config( + } + } + +- if (avl_insert(&inst->inst_attrs, a, ainfo_cmp, ainfo_dup) != 0) { ++ if (avl_insert(&inst->inst_attrs, (caddr_t)a, ainfo_cmp, ainfo_dup) != 0) { + /* duplicate - existing version updated */ + attrinfo_delete(&a); + } +@@ -964,7 +970,7 @@ attr_create_empty(backend *be, char *type, struct attrinfo **ai) + struct attrinfo *a = attrinfo_new(); + slapi_attr_init(&a->ai_sattr, type); + a->ai_type = slapi_ch_strdup(type); +- if (avl_insert(&inst->inst_attrs, a, ainfo_cmp, ainfo_dup) != 0) { ++ if (avl_insert(&inst->inst_attrs, (caddr_t)a, ainfo_cmp, ainfo_dup) != 0) { + /* duplicate - existing version updated */ + attrinfo_delete(&a); + ainfo_get(be, type, &a); +diff --git a/ldap/servers/slapd/back-ldbm/matchrule.c b/ldap/servers/slapd/back-ldbm/matchrule.c +index 5365e8acf..85ff87b95 100644 +--- a/ldap/servers/slapd/back-ldbm/matchrule.c ++++ b/ldap/servers/slapd/back-ldbm/matchrule.c +@@ -79,7 +79,7 @@ int + destroy_matchrule_indexer(Slapi_PBlock *pb) + { + Slapi_Value **keys = NULL; +- IFP mrDESTROY = NULL; ++ int32_t (*mrDESTROY)(Slapi_PBlock *) = NULL; + if (!slapi_pblock_get(pb, SLAPI_PLUGIN_DESTROY_FN, &mrDESTROY)) { + if (mrDESTROY != NULL) { + mrDESTROY(pb); +@@ -109,7 +109,7 @@ destroy_matchrule_indexer(Slapi_PBlock *pb) + int + matchrule_values_to_keys(Slapi_PBlock *pb, Slapi_Value **input_values, struct berval ***output_values) + { +- IFP mrINDEX = NULL; ++ int32_t (*mrINDEX)(Slapi_PBlock *) = NULL; + + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_FN, &mrINDEX); + slapi_pblock_set(pb, SLAPI_PLUGIN_MR_VALUES, input_values); +@@ -130,7 +130,7 @@ matchrule_values_to_keys(Slapi_PBlock *pb, Slapi_Value **input_values, struct be + int + matchrule_values_to_keys_sv(Slapi_PBlock *pb, Slapi_Value **input_values, Slapi_Value ***output_values) + { +- IFP mrINDEX = NULL; ++ int32_t (*mrINDEX)(Slapi_PBlock *) = NULL; + + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_SV_FN, &mrINDEX); + if (NULL == mrINDEX) { /* old school - does not have SV function */ +diff --git a/ldap/servers/slapd/back-ldbm/misc.c b/ldap/servers/slapd/back-ldbm/misc.c +index 309cc8a94..4ced0f1da 100644 +--- a/ldap/servers/slapd/back-ldbm/misc.c ++++ b/ldap/servers/slapd/back-ldbm/misc.c +@@ -329,7 +329,7 @@ ldbm_txn_ruv_modify_context(Slapi_PBlock *pb, modify_context *mc) + Slapi_Mods *smods = NULL; + struct backentry *bentry; + entry_address bentry_addr; +- IFP fn = NULL; ++ int32_t (*fn)(Slapi_PBlock *, char **, Slapi_Mods **) = NULL; + int rc = 0; + back_txn txn = {NULL}; + +diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h +index f16c37d73..29d196f39 100644 +--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h ++++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h +@@ -499,7 +499,7 @@ int vlv_trim_candidates_txn(backend *be, const IDList *candidates, const sort_sp + int vlv_trim_candidates(backend *be, const IDList *candidates, const sort_spec *sort_control, const struct vlv_request *vlv_request_control, IDList **filteredCandidates, struct vlv_response *pResponse); + int vlv_parse_request_control(backend *be, struct berval *vlv_spec_ber, struct vlv_request *vlvp); + int vlv_make_response_control(Slapi_PBlock *pb, const struct vlv_response *vlvp); +-void vlv_getindices(IFP callback_fn, void *param, backend *be); ++void vlv_getindices(int32_t (*callback_fn)(caddr_t, caddr_t), void *param, backend *be); + void vlv_print_access_log(Slapi_PBlock *pb, struct vlv_request *vlvi, struct vlv_response *vlvo); + void vlv_grok_new_import_entry(const struct backentry *e, backend *be, int *seen_them_all); + IDList *vlv_find_index_by_filter(struct backend *be, const char *base, Slapi_Filter *f); +diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c +index a1cd226f4..b5dd1ea55 100644 +--- a/ldap/servers/slapd/back-ldbm/vlv.c ++++ b/ldap/servers/slapd/back-ldbm/vlv.c +@@ -673,7 +673,7 @@ vlv_getindexnames(backend *be) + /* Return the list of VLV indices to the import code. Added read lock */ + + void +-vlv_getindices(IFP callback_fn, void *param, backend *be) ++vlv_getindices(int32_t (*callback_fn)(caddr_t, caddr_t), void *param, backend *be) + { + /* Traverse the list, calling the import code's callback function */ + struct vlvSearch *ps = NULL; +@@ -683,7 +683,7 @@ vlv_getindices(IFP callback_fn, void *param, backend *be) + for (; ps != NULL; ps = ps->vlv_next) { + struct vlvIndex *pi = ps->vlv_index; + for (; pi != NULL; pi = pi->vlv_next) { +- callback_fn(pi->vlv_attrinfo, param); ++ callback_fn((caddr_t)(pi->vlv_attrinfo), (caddr_t)param); + } + } + slapi_rwlock_unlock(be->vlvSearchList_lock); +diff --git a/ldap/servers/slapd/backend.c b/ldap/servers/slapd/backend.c +index 0a2555a9b..cf3c2ebbc 100644 +--- a/ldap/servers/slapd/backend.c ++++ b/ldap/servers/slapd/backend.c +@@ -502,6 +502,7 @@ slapi_be_getentrypoint(Slapi_Backend *be, int entrypoint, void **ret_fnptr, Slap + return 0; + } + ++ + int + slapi_be_setentrypoint(Slapi_Backend *be, int entrypoint, void *ret_fnptr, Slapi_PBlock *pb) + { +@@ -517,61 +518,61 @@ slapi_be_setentrypoint(Slapi_Backend *be, int entrypoint, void *ret_fnptr, Slapi + + switch (entrypoint) { + case SLAPI_PLUGIN_DB_BIND_FN: +- be->be_bind = (IFP)ret_fnptr; ++ be->be_bind = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_UNBIND_FN: +- be->be_unbind = (IFP)ret_fnptr; ++ be->be_unbind = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_SEARCH_FN: +- be->be_search = (IFP)ret_fnptr; ++ be->be_search = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_COMPARE_FN: +- be->be_compare = (IFP)ret_fnptr; ++ be->be_compare = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_MODIFY_FN: +- be->be_modify = (IFP)ret_fnptr; ++ be->be_modify = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_MODRDN_FN: +- be->be_modrdn = (IFP)ret_fnptr; ++ be->be_modrdn = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_ADD_FN: +- be->be_add = (IFP)ret_fnptr; ++ be->be_add = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_DELETE_FN: +- be->be_delete = (IFP)ret_fnptr; ++ be->be_delete = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_ABANDON_FN: +- be->be_abandon = (IFP)ret_fnptr; ++ be->be_abandon = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_CONFIG_FN: +- be->be_config = (IFP)ret_fnptr; ++ be->be_config = ret_fnptr; + break; + case SLAPI_PLUGIN_CLOSE_FN: +- be->be_close = (IFP)ret_fnptr; ++ be->be_close = ret_fnptr; + break; + case SLAPI_PLUGIN_START_FN: +- be->be_start = (IFP)ret_fnptr; ++ be->be_start = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_RESULT_FN: +- be->be_result = (IFP)ret_fnptr; ++ be->be_result = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_LDIF2DB_FN: +- be->be_ldif2db = (IFP)ret_fnptr; ++ be->be_ldif2db = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_DB2LDIF_FN: +- be->be_db2ldif = (IFP)ret_fnptr; ++ be->be_db2ldif = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_ARCHIVE2DB_FN: +- be->be_archive2db = (IFP)ret_fnptr; ++ be->be_archive2db = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_DB2ARCHIVE_FN: +- be->be_db2archive = (IFP)ret_fnptr; ++ be->be_db2archive = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN: +- be->be_next_search_entry = (IFP)ret_fnptr; ++ be->be_next_search_entry = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_EXT_FN: +- be->be_next_search_entry_ext = (IFP)ret_fnptr; ++ be->be_next_search_entry_ext = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_SEARCH_RESULTS_RELEASE_FN: + be->be_search_results_release = (VFPP)ret_fnptr; +@@ -580,19 +581,19 @@ slapi_be_setentrypoint(Slapi_Backend *be, int entrypoint, void *ret_fnptr, Slapi + be->be_prev_search_results = (VFP)ret_fnptr; + break; + case SLAPI_PLUGIN_DB_TEST_FN: +- be->be_dbtest = (IFP)ret_fnptr; ++ be->be_dbtest = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_RMDB_FN: +- be->be_rmdb = (IFP)ret_fnptr; ++ be->be_rmdb = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_SEQ_FN: +- be->be_seq = (IFP)ret_fnptr; ++ be->be_seq = ret_fnptr; + break; + case SLAPI_PLUGIN_DB_DB2INDEX_FN: +- be->be_db2index = (IFP)ret_fnptr; ++ be->be_db2index = ret_fnptr; + break; + case SLAPI_PLUGIN_CLEANUP_FN: +- be->be_cleanup = (IFP)ret_fnptr; ++ be->be_cleanup = ret_fnptr; + break; + default: + slapi_log_err(SLAPI_LOG_ERR, "slapi_be_setentrypoint", +@@ -682,7 +683,7 @@ slapi_back_ctrl_info(Slapi_Backend *be, int cmd, void *info) + int + slapi_back_transaction_begin(Slapi_PBlock *pb) + { +- IFP txn_begin; ++ int32_t (*txn_begin)(Slapi_PBlock *); + if (slapi_pblock_get(pb, SLAPI_PLUGIN_DB_BEGIN_FN, (void *)&txn_begin) || + !txn_begin) { + return SLAPI_BACK_TRANSACTION_NOT_SUPPORTED; +@@ -695,7 +696,7 @@ slapi_back_transaction_begin(Slapi_PBlock *pb) + int + slapi_back_transaction_commit(Slapi_PBlock *pb) + { +- IFP txn_commit; ++ int32_t (*txn_commit)(Slapi_PBlock *); + if (slapi_pblock_get(pb, SLAPI_PLUGIN_DB_COMMIT_FN, (void *)&txn_commit) || + !txn_commit) { + return SLAPI_BACK_TRANSACTION_NOT_SUPPORTED; +@@ -708,7 +709,7 @@ slapi_back_transaction_commit(Slapi_PBlock *pb) + int + slapi_back_transaction_abort(Slapi_PBlock *pb) + { +- IFP txn_abort; ++ int32_t (*txn_abort)(Slapi_PBlock *); + if (slapi_pblock_get(pb, SLAPI_PLUGIN_DB_ABORT_FN, (void *)&txn_abort) || + !txn_abort) { + return SLAPI_BACK_TRANSACTION_NOT_SUPPORTED; +diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c +index a0db367b2..bec3e32f4 100644 +--- a/ldap/servers/slapd/dse.c ++++ b/ldap/servers/slapd/dse.c +@@ -120,7 +120,7 @@ typedef struct dse_search_set + + static int dse_permission_to_write(struct dse *pdse, int loglevel); + static int dse_write_file_nolock(struct dse *pdse); +-static int dse_apply_nolock(struct dse *pdse, IFP fp, caddr_t arg); ++static int dse_apply_nolock(struct dse *pdse, int32_t (*fp)(caddr_t, caddr_t), caddr_t arg); + static int dse_replace_entry(struct dse *pdse, Slapi_Entry *e, int write_file, int use_lock); + static dse_search_set *dse_search_set_new(void); + static void dse_search_set_delete(dse_search_set *ss); +@@ -257,7 +257,7 @@ dse_find_node(struct dse *pdse, const Slapi_DN *dn) + slapi_entry_set_sdn(fe, dn); + searchNode.entry = fe; + +- n = (struct dse_node *)avl_find(pdse->dse_tree, &searchNode, entry_dn_cmp); ++ n = (struct dse_node *)avl_find(pdse->dse_tree, (caddr_t)&searchNode, entry_dn_cmp); + + slapi_entry_free(fe); + } +@@ -491,7 +491,7 @@ dse_new_with_filelist(char *filename, char *tmpfilename, char *backfilename, cha + } + + static int +-dse_internal_delete_entry(caddr_t data, caddr_t arg __attribute__((unused))) ++dse_internal_delete_entry(caddr_t data) + { + struct dse_node *n = (struct dse_node *)data; + dse_node_delete(&n); +@@ -1290,9 +1290,9 @@ dse_add_entry_pb(struct dse *pdse, Slapi_Entry *e, Slapi_PBlock *pb) + /* keep write lock during both tree update and file write operations */ + dse_lock_write(pdse, DSE_USE_LOCK); + if (merge) { +- rc = avl_insert(&(pdse->dse_tree), n, entry_dn_cmp, dupentry_merge); ++ rc = avl_insert(&(pdse->dse_tree), (caddr_t)n, entry_dn_cmp, dupentry_merge); + } else { +- rc = avl_insert(&(pdse->dse_tree), n, entry_dn_cmp, dupentry_disallow); ++ rc = avl_insert(&(pdse->dse_tree), (caddr_t)n, entry_dn_cmp, dupentry_disallow); + } + if (-1 != rc) { + /* update num sub of parent with no lock; we already hold the write lock */ +@@ -1479,7 +1479,7 @@ dse_replace_entry(struct dse *pdse, Slapi_Entry *e, int write_file, int use_lock + if (NULL != e) { + struct dse_node *n = dse_node_new(e); + dse_lock_write(pdse, use_lock); +- rc = avl_insert(&(pdse->dse_tree), n, entry_dn_cmp, dupentry_replace); ++ rc = avl_insert(&(pdse->dse_tree), (caddr_t)n, entry_dn_cmp, dupentry_replace); + if (write_file) + dse_write_file_nolock(pdse); + /* If the entry was replaced i.e. not added as a new entry, we need to +@@ -1554,7 +1554,7 @@ dse_read_next_entry(char *buf, char **lastp) + * searching, a read lock, for modifying in place, a write lock + */ + static int +-dse_apply_nolock(struct dse *pdse, IFP fp, caddr_t arg) ++dse_apply_nolock(struct dse *pdse, int32_t (*fp)(caddr_t, caddr_t), caddr_t arg) + { + avl_apply(pdse->dse_tree, fp, arg, STOP_TRAVERSAL, AVL_INORDER); + return 1; +@@ -1576,7 +1576,7 @@ dse_delete_entry(struct dse *pdse, Slapi_PBlock *pb, const Slapi_Entry *e) + + /* keep write lock for both tree deleting and file writing */ + dse_lock_write(pdse, DSE_USE_LOCK); +- if ((deleted_node = (struct dse_node *)avl_delete(&pdse->dse_tree, n, entry_dn_cmp))) { ++ if ((deleted_node = (struct dse_node *)avl_delete(&pdse->dse_tree, (caddr_t)n, entry_dn_cmp))) { + dse_node_delete(&deleted_node); + } + dse_node_delete(&n); +diff --git a/ldap/servers/slapd/entry.c b/ldap/servers/slapd/entry.c +index 235410e45..6040eb419 100644 +--- a/ldap/servers/slapd/entry.c ++++ b/ldap/servers/slapd/entry.c +@@ -709,7 +709,7 @@ entry_attrs_add(entry_attrs *ea, const char *atname, int atarrayindex) + ead->ead_attrarrayindex = atarrayindex; + ead->ead_attrtypename = atname; /* a reference, not a strdup! */ + +- avl_insert(&(ea->ea_attrlist), ead, attr_type_node_cmp, avl_dup_error); ++ avl_insert(&(ea->ea_attrlist), (caddr_t)ead, attr_type_node_cmp, avl_dup_error); + } + + /* +@@ -723,7 +723,7 @@ entry_attrs_find(entry_attrs *ea, char *type) + entry_attr_data *foundead; + + tmpead.ead_attrtypename = type; +- foundead = (entry_attr_data *)avl_find(ea->ea_attrlist, &tmpead, ++ foundead = (entry_attr_data *)avl_find(ea->ea_attrlist, (caddr_t)&tmpead, + attr_type_node_cmp); + return (NULL != foundead) ? foundead->ead_attrarrayindex : -1; + } +diff --git a/ldap/servers/slapd/filtercmp.c b/ldap/servers/slapd/filtercmp.c +index 3e17796e9..0d8764b5a 100644 +--- a/ldap/servers/slapd/filtercmp.c ++++ b/ldap/servers/slapd/filtercmp.c +@@ -86,7 +86,7 @@ get_mr_normval(char *oid, char *type, struct berval **inval, struct berval ***ou + { + Slapi_PBlock *pb = slapi_pblock_new(); + unsigned int sort_indicator = SLAPI_PLUGIN_MR_USAGE_SORT; +- IFP mrIndex = NULL; ++ int32_t (*mrIndex)(Slapi_PBlock *) = NULL; + + if (!pb) { + return NULL; +@@ -118,7 +118,7 @@ get_mr_normval(char *oid, char *type, struct berval **inval, struct berval ***ou + static void + done_mr_normval(Slapi_PBlock *pb) + { +- IFP mrDestroy = NULL; ++ int32_t (*mrDestroy)(Slapi_PBlock *) = NULL; + + if (slapi_pblock_get(pb, SLAPI_PLUGIN_DESTROY_FN, &mrDestroy) == 0) { + if (mrDestroy) +diff --git a/ldap/servers/slapd/getfilelist.c b/ldap/servers/slapd/getfilelist.c +index dd5deb282..2fd164baf 100644 +--- a/ldap/servers/slapd/getfilelist.c ++++ b/ldap/servers/slapd/getfilelist.c +@@ -46,9 +46,11 @@ struct path_wrapper + int order; + }; + +-static int +-path_wrapper_cmp(struct path_wrapper *p1, struct path_wrapper *p2) ++static int32_t ++path_wrapper_cmp(caddr_t pwc1, caddr_t pwc2) + { ++ struct path_wrapper *p1 = (struct path_wrapper *)pwc1; ++ struct path_wrapper *p2 = (struct path_wrapper *)pwc2; + if (p1->order < p2->order) { + /* p1 is "earlier" so put it first */ + return -1; +@@ -217,7 +219,7 @@ get_filelist( + pw_ptr->path = slapi_ch_smprintf("%s/%s", dirname, dirent->name); + pw_ptr->filename = slapi_ch_strdup(dirent->name); + pw_ptr->order = i; +- avl_insert(&filetree, pw_ptr, path_wrapper_cmp, 0); ++ avl_insert(&filetree, (caddr_t)pw_ptr, path_wrapper_cmp, 0); + num++; + } + } +diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c +index 3801326c4..76e26cb86 100644 +--- a/ldap/servers/slapd/pblock.c ++++ b/ldap/servers/slapd/pblock.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) 2005-2025 Red Hat, Inc. + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * All rights reserved. + * +@@ -783,25 +783,25 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_bind; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_bind; + break; + case SLAPI_PLUGIN_DB_UNBIND_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_unbind; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_unbind; + break; + case SLAPI_PLUGIN_DB_SEARCH_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_search; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_search; + break; + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_next_search_entry; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_next_search_entry; + break; + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_EXT_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { +@@ -825,37 +825,37 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_compare; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_compare; + break; + case SLAPI_PLUGIN_DB_MODIFY_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_modify; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_modify; + break; + case SLAPI_PLUGIN_DB_MODRDN_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_modrdn; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_modrdn; + break; + case SLAPI_PLUGIN_DB_ADD_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_add; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_add; + break; + case SLAPI_PLUGIN_DB_DELETE_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_delete; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_delete; + break; + case SLAPI_PLUGIN_DB_ABANDON_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_abandon; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_abandon; + break; + case SLAPI_PLUGIN_DB_CONFIG_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { +@@ -867,7 +867,7 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + (*(IFP *)value) = pblock->pb_plugin->plg_close; + break; + case SLAPI_PLUGIN_CLEANUP_FN: +- (*(IFP *)value) = pblock->pb_plugin->plg_cleanup; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_cleanup; + break; + case SLAPI_PLUGIN_START_FN: + (*(IFP *)value) = pblock->pb_plugin->plg_start; +@@ -876,22 +876,22 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + (*(IFP *)value) = pblock->pb_plugin->plg_poststart; + break; + case SLAPI_PLUGIN_DB_WIRE_IMPORT_FN: +- (*(IFP *)value) = pblock->pb_plugin->plg_wire_import; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_wire_import; + break; + case SLAPI_PLUGIN_DB_GET_INFO_FN: +- (*(IFP *)value) = pblock->pb_plugin->plg_get_info; ++ (*(int32_t (**)(struct backend *, int32_t, void **))value) = pblock->pb_plugin->plg_get_info; + break; + case SLAPI_PLUGIN_DB_SET_INFO_FN: +- (*(IFP *)value) = pblock->pb_plugin->plg_set_info; ++ (*(int32_t (**)(struct backend *, int32_t, void **))value) = pblock->pb_plugin->plg_set_info; + break; + case SLAPI_PLUGIN_DB_CTRL_INFO_FN: +- (*(IFP *)value) = pblock->pb_plugin->plg_ctrl_info; ++ (*(int32_t (**)(struct backend *, int32_t, void **))value) = pblock->pb_plugin->plg_ctrl_info; + break; + case SLAPI_PLUGIN_DB_SEQ_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_seq; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_seq; + break; + case SLAPI_PLUGIN_DB_ENTRY_FN: + (*(IFP *)value) = SLAPI_PBLOCK_GET_PLUGIN_RELATED_POINTER(pblock, +@@ -915,55 +915,55 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_ldif2db; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_ldif2db; + break; + case SLAPI_PLUGIN_DB_DB2LDIF_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_db2ldif; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_db2ldif; + break; + case SLAPI_PLUGIN_DB_COMPACT_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_dbcompact; ++ (*(int32_t (**)(struct backend *, bool))value) = pblock->pb_plugin->plg_dbcompact; + break; + case SLAPI_PLUGIN_DB_DB2INDEX_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_db2index; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_db2index; + break; + case SLAPI_PLUGIN_DB_ARCHIVE2DB_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_archive2db; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_archive2db; + break; + case SLAPI_PLUGIN_DB_DB2ARCHIVE_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_db2archive; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_db2archive; + break; + case SLAPI_PLUGIN_DB_UPGRADEDB_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_upgradedb; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_upgradedb; + break; + case SLAPI_PLUGIN_DB_UPGRADEDNFORMAT_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_upgradednformat; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_upgradednformat; + break; + case SLAPI_PLUGIN_DB_DBVERIFY_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_dbverify; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_dbverify; + break; + case SLAPI_PLUGIN_DB_BEGIN_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { +@@ -1007,7 +1007,7 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNEXTENDEDOP) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_exhandler; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_exhandler; + break; + case SLAPI_PLUGIN_EXT_OP_OIDLIST: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_EXTENDEDOP && +@@ -1028,7 +1028,7 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNEXTENDEDOP) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_be_exhandler; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Backend **))value) = pblock->pb_plugin->plg_be_exhandler; + break; + + /* preoperation plugin functions */ +@@ -1474,31 +1474,32 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_syntax_filter_ava; ++ (*(int32_t (**)(Slapi_PBlock *, const struct berval *, Slapi_Value **, ++ int32_t, Slapi_Value **))value) = pblock->pb_plugin->plg_syntax_filter_ava; + break; + case SLAPI_PLUGIN_SYNTAX_FILTER_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_syntax_filter_sub; ++ (*(int32_t (**)(Slapi_PBlock*, char *, char **, char *, Slapi_Value**))value) = pblock->pb_plugin->plg_syntax_filter_sub; + break; + case SLAPI_PLUGIN_SYNTAX_VALUES2KEYS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_syntax_values2keys; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Value **, Slapi_Value ***, int32_t))value) = pblock->pb_plugin->plg_syntax_values2keys; + break; + case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_syntax_assertion2keys_ava; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Value *, Slapi_Value ***, int32_t))value) = pblock->pb_plugin->plg_syntax_assertion2keys_ava; + break; + case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_syntax_assertion2keys_sub; ++ (*(int32_t (**)(Slapi_PBlock *, char *, char **, char *, Slapi_Value ***))value) = pblock->pb_plugin->plg_syntax_assertion2keys_sub; + break; + case SLAPI_PLUGIN_SYNTAX_NAMES: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { +@@ -1535,13 +1536,13 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_syntax_validate; ++ (*(int32_t (**)(struct berval *))value) = pblock->pb_plugin->plg_syntax_validate; + break; + case SLAPI_PLUGIN_SYNTAX_NORMALIZE: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- (*(VFPV *)value) = pblock->pb_plugin->plg_syntax_normalize; ++ (*(void (**)(Slapi_PBlock *, char *, int32_t, char **))value) = pblock->pb_plugin->plg_syntax_normalize; + break; + + /* controls we know about */ +@@ -1799,11 +1800,12 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + /* matching rule plugin functions */ + case SLAPI_PLUGIN_MR_FILTER_CREATE_FN: + SLAPI_PLUGIN_TYPE_CHECK(pblock, SLAPI_PLUGIN_MATCHINGRULE); +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_filter_create; ++ ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_mr_filter_create; + break; + case SLAPI_PLUGIN_MR_INDEXER_CREATE_FN: + SLAPI_PLUGIN_TYPE_CHECK(pblock, SLAPI_PLUGIN_MATCHINGRULE); +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_indexer_create; ++ (*(int32_t (**)(Slapi_PBlock *))value) = pblock->pb_plugin->plg_mr_indexer_create; + break; + case SLAPI_PLUGIN_MR_FILTER_MATCH_FN: + if (pblock->pb_mr != NULL) { +@@ -1904,31 +1906,31 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_filter_ava; ++ (*(int32_t (**)(Slapi_PBlock *, const struct berval *, Slapi_Value **, int32_t, Slapi_Value **))value) = pblock->pb_plugin->plg_mr_filter_ava; + break; + case SLAPI_PLUGIN_MR_FILTER_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_filter_sub; ++ (*(int32_t (**)(Slapi_PBlock *, char *, char **, char*, Slapi_Value **))value) = pblock->pb_plugin->plg_mr_filter_sub; + break; + case SLAPI_PLUGIN_MR_VALUES2KEYS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_values2keys; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Value **, Slapi_Value ***, int32_t))value) = pblock->pb_plugin->plg_mr_values2keys; + break; + case SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_assertion2keys_ava; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Value *, Slapi_Value ***, int32_t))value) = pblock->pb_plugin->plg_mr_assertion2keys_ava; + break; + case SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_assertion2keys_sub; ++ (*(int32_t (**)(Slapi_PBlock *, char *, char **, char *, Slapi_Value ***))value) = pblock->pb_plugin->plg_mr_assertion2keys_sub; + break; + case SLAPI_PLUGIN_MR_FLAGS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { +@@ -1946,13 +1948,13 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(IFP *)value) = pblock->pb_plugin->plg_mr_compare; ++ (*(int32_t (**)(struct berval *, struct berval *))value) = pblock->pb_plugin->plg_mr_compare; + break; + case SLAPI_PLUGIN_MR_NORMALIZE: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- (*(VFPV *)value) = pblock->pb_plugin->plg_mr_normalize; ++ (*(void (**)(Slapi_PBlock *, char *, int32_t, char **))value) = pblock->pb_plugin->plg_mr_normalize; + break; + + /* seq arguments */ +@@ -2145,9 +2147,9 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + break; + case SLAPI_TXN_RUV_MODS_FN: + if (pblock->pb_intop != NULL) { +- (*(IFP *)value) = pblock->pb_intop->pb_txn_ruv_mods_fn; ++ (*(int32_t(**)(Slapi_PBlock *, char **, Slapi_Mods **))value) = pblock->pb_intop->pb_txn_ruv_mods_fn; + } else { +- (*(IFP *)value) = NULL; ++ (*(int32_t(**)(Slapi_PBlock *, char **, Slapi_Mods **))value) = NULL; + } + break; + +@@ -2221,24 +2223,25 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + (*(IFP *)value) = pblock->pb_plugin->plg_acl_init; + break; + case SLAPI_PLUGIN_ACL_SYNTAX_CHECK: +- (*(IFP *)value) = pblock->pb_plugin->plg_acl_syntax_check; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Entry *, char **))value) = pblock->pb_plugin->plg_acl_syntax_check; + break; + case SLAPI_PLUGIN_ACL_ALLOW_ACCESS: +- (*(IFP *)value) = pblock->pb_plugin->plg_acl_access_allowed; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Entry *, char **, struct berval *, ++ int32_t, int32_t, char **))value) = pblock->pb_plugin->plg_acl_access_allowed; + break; + case SLAPI_PLUGIN_ACL_MODS_ALLOWED: +- (*(IFP *)value) = pblock->pb_plugin->plg_acl_mods_allowed; ++ (*(int32_t (**)(Slapi_PBlock *, Slapi_Entry *, LDAPMod **, void *))value) = pblock->pb_plugin->plg_acl_mods_allowed; + break; + case SLAPI_PLUGIN_ACL_MODS_UPDATE: +- (*(IFP *)value) = pblock->pb_plugin->plg_acl_mods_update; ++ (*(int32_t (**)(Slapi_PBlock *, int32_t, Slapi_DN *, void *))value) = pblock->pb_plugin->plg_acl_mods_update; + break; + /* MMR Plugin */ + case SLAPI_PLUGIN_MMR_BETXN_PREOP: +- (*(IFP *)value) = pblock->pb_plugin->plg_mmr_betxn_preop; +- break; ++ (*(int32_t (**)(Slapi_PBlock *, int32_t))value) = pblock->pb_plugin->plg_mmr_betxn_preop; ++ break; + case SLAPI_PLUGIN_MMR_BETXN_POSTOP: +- (*(IFP *)value) = pblock->pb_plugin->plg_mmr_betxn_postop; +- break; ++ (*(int32_t (**)(Slapi_PBlock *, int32_t))value) = pblock->pb_plugin->plg_mmr_betxn_postop; ++ break; + + case SLAPI_REQUESTOR_DN: + /* NOTE: It's not a copy of the DN */ +@@ -2362,11 +2365,11 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + + /* entry fetch/store plugin */ + case SLAPI_PLUGIN_ENTRY_FETCH_FUNC: +- (*(IFP *)value) = pblock->pb_plugin->plg_entryfetchfunc; ++ (*(int32_t (**)(char **, uint32_t *))value) = pblock->pb_plugin->plg_entryfetchfunc; + break; + + case SLAPI_PLUGIN_ENTRY_STORE_FUNC: +- (*(IFP *)value) = pblock->pb_plugin->plg_entrystorefunc; ++ (*(int32_t (**)(char **, uint32_t *))value) = pblock->pb_plugin->plg_entrystorefunc; + break; + + case SLAPI_PLUGIN_ENABLED: +@@ -2723,25 +2726,25 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_bind = (IFP)value; ++ pblock->pb_plugin->plg_bind = value; + break; + case SLAPI_PLUGIN_DB_UNBIND_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_unbind = (IFP)value; ++ pblock->pb_plugin->plg_unbind = value; + break; + case SLAPI_PLUGIN_DB_SEARCH_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_search = (IFP)value; ++ pblock->pb_plugin->plg_search = value; + break; + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_next_search_entry = (IFP)value; ++ pblock->pb_plugin->plg_next_search_entry = value; + break; + case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_EXT_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { +@@ -2765,37 +2768,37 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_compare = (IFP)value; ++ pblock->pb_plugin->plg_compare = value; + break; + case SLAPI_PLUGIN_DB_MODIFY_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_modify = (IFP)value; ++ pblock->pb_plugin->plg_modify = value; + break; + case SLAPI_PLUGIN_DB_MODRDN_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_modrdn = (IFP)value; ++ pblock->pb_plugin->plg_modrdn = value; + break; + case SLAPI_PLUGIN_DB_ADD_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_add = (IFP)value; ++ pblock->pb_plugin->plg_add = value; + break; + case SLAPI_PLUGIN_DB_DELETE_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_delete = (IFP)value; ++ pblock->pb_plugin->plg_delete = value; + break; + case SLAPI_PLUGIN_DB_ABANDON_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_abandon = (IFP)value; ++ pblock->pb_plugin->plg_abandon = value; + break; + case SLAPI_PLUGIN_DB_CONFIG_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { +@@ -2807,7 +2810,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_plugin->plg_close = (IFP)value; + break; + case SLAPI_PLUGIN_CLEANUP_FN: +- pblock->pb_plugin->plg_cleanup = (IFP)value; ++ pblock->pb_plugin->plg_cleanup = value; + break; + case SLAPI_PLUGIN_START_FN: + pblock->pb_plugin->plg_start = (IFP)value; +@@ -2816,22 +2819,22 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_plugin->plg_poststart = (IFP)value; + break; + case SLAPI_PLUGIN_DB_WIRE_IMPORT_FN: +- pblock->pb_plugin->plg_wire_import = (IFP)value; ++ pblock->pb_plugin->plg_wire_import = value; + break; + case SLAPI_PLUGIN_DB_GET_INFO_FN: +- pblock->pb_plugin->plg_get_info = (IFP)value; ++ pblock->pb_plugin->plg_get_info = value; + break; + case SLAPI_PLUGIN_DB_SET_INFO_FN: +- pblock->pb_plugin->plg_set_info = (IFP)value; ++ pblock->pb_plugin->plg_set_info = value; + break; + case SLAPI_PLUGIN_DB_CTRL_INFO_FN: +- pblock->pb_plugin->plg_ctrl_info = (IFP)value; ++ pblock->pb_plugin->plg_ctrl_info = value; + break; + case SLAPI_PLUGIN_DB_SEQ_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_seq = (IFP)value; ++ pblock->pb_plugin->plg_seq = value; + break; + case SLAPI_PLUGIN_DB_ENTRY_FN: + pblock->pb_plugin->plg_entry = (IFP)value; +@@ -2852,49 +2855,49 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_ldif2db = (IFP)value; ++ pblock->pb_plugin->plg_ldif2db = value; + break; + case SLAPI_PLUGIN_DB_DB2LDIF_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_db2ldif = (IFP)value; ++ pblock->pb_plugin->plg_db2ldif = value; + break; + case SLAPI_PLUGIN_DB_DB2INDEX_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_db2index = (IFP)value; ++ pblock->pb_plugin->plg_db2index = value; + break; + case SLAPI_PLUGIN_DB_ARCHIVE2DB_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_archive2db = (IFP)value; ++ pblock->pb_plugin->plg_archive2db = value; + break; + case SLAPI_PLUGIN_DB_DB2ARCHIVE_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_db2archive = (IFP)value; ++ pblock->pb_plugin->plg_db2archive = value; + break; + case SLAPI_PLUGIN_DB_UPGRADEDB_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_upgradedb = (IFP)value; ++ pblock->pb_plugin->plg_upgradedb = value; + break; + case SLAPI_PLUGIN_DB_UPGRADEDNFORMAT_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_upgradednformat = (IFP)value; ++ pblock->pb_plugin->plg_upgradednformat = value; + break; + case SLAPI_PLUGIN_DB_DBVERIFY_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_dbverify = (IFP)value; ++ pblock->pb_plugin->plg_dbverify = value; + break; + case SLAPI_PLUGIN_DB_BEGIN_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { +@@ -2934,7 +2937,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); + } +- pblock->pb_plugin->plg_dbcompact = (IFP)value; ++ pblock->pb_plugin->plg_dbcompact = value; + break; + + /* extendedop plugin functions */ +@@ -2943,7 +2946,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNEXTENDEDOP) { + return (-1); + } +- pblock->pb_plugin->plg_exhandler = (IFP)value; ++ pblock->pb_plugin->plg_exhandler = value; + break; + case SLAPI_PLUGIN_EXT_OP_OIDLIST: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_EXTENDEDOP && +@@ -2965,7 +2968,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNEXTENDEDOP) { + return (-1); + } +- pblock->pb_plugin->plg_be_exhandler = (IFP)value; ++ pblock->pb_plugin->plg_be_exhandler = value; + break; + + /* preoperation plugin functions */ +@@ -3331,31 +3334,31 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_filter_ava = (IFP)value; ++ pblock->pb_plugin->plg_syntax_filter_ava = value; + break; + case SLAPI_PLUGIN_SYNTAX_FILTER_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_filter_sub = (IFP)value; ++ pblock->pb_plugin->plg_syntax_filter_sub = value; + break; + case SLAPI_PLUGIN_SYNTAX_VALUES2KEYS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_values2keys = (IFP)value; ++ pblock->pb_plugin->plg_syntax_values2keys = value; + break; + case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_assertion2keys_ava = (IFP)value; ++ pblock->pb_plugin->plg_syntax_assertion2keys_ava = value; + break; + case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_assertion2keys_sub = (IFP)value; ++ pblock->pb_plugin->plg_syntax_assertion2keys_sub = value; + break; + case SLAPI_PLUGIN_SYNTAX_NAMES: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { +@@ -3391,13 +3394,13 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_validate = (IFP)value; ++ pblock->pb_plugin->plg_syntax_validate = value; + break; + case SLAPI_PLUGIN_SYNTAX_NORMALIZE: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_normalize = (VFPV)value; ++ pblock->pb_plugin->plg_syntax_normalize = value; + break; + case SLAPI_ENTRY_PRE_OP: + _pblock_assert_pb_intop(pblock); +@@ -3723,11 +3726,11 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + /* matching rule plugin functions */ + case SLAPI_PLUGIN_MR_FILTER_CREATE_FN: + SLAPI_PLUGIN_TYPE_CHECK(pblock, SLAPI_PLUGIN_MATCHINGRULE); +- pblock->pb_plugin->plg_mr_filter_create = (IFP)value; ++ pblock->pb_plugin->plg_mr_filter_create = value; + break; + case SLAPI_PLUGIN_MR_INDEXER_CREATE_FN: + SLAPI_PLUGIN_TYPE_CHECK(pblock, SLAPI_PLUGIN_MATCHINGRULE); +- pblock->pb_plugin->plg_mr_indexer_create = (IFP)value; ++ pblock->pb_plugin->plg_mr_indexer_create = value; + break; + case SLAPI_PLUGIN_MR_FILTER_MATCH_FN: + _pblock_assert_pb_mr(pblock); +@@ -3789,31 +3792,31 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_filter_ava = (IFP)value; ++ pblock->pb_plugin->plg_mr_filter_ava = value; + break; + case SLAPI_PLUGIN_MR_FILTER_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_filter_sub = (IFP)value; ++ pblock->pb_plugin->plg_mr_filter_sub = value; + break; + case SLAPI_PLUGIN_MR_VALUES2KEYS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_values2keys = (IFP)value; ++ pblock->pb_plugin->plg_mr_values2keys = value; + break; + case SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_assertion2keys_ava = (IFP)value; ++ pblock->pb_plugin->plg_mr_assertion2keys_ava = value; + break; + case SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_assertion2keys_sub = (IFP)value; ++ pblock->pb_plugin->plg_mr_assertion2keys_sub = value; + break; + case SLAPI_PLUGIN_MR_FLAGS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { +@@ -3832,13 +3835,13 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_compare = (IFP)value; ++ pblock->pb_plugin->plg_mr_compare = value; + break; + case SLAPI_PLUGIN_MR_NORMALIZE: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_normalize = (VFPV)value; ++ pblock->pb_plugin->plg_mr_normalize = value; + break; + + /* seq arguments */ +@@ -3957,7 +3960,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + break; + case SLAPI_TXN_RUV_MODS_FN: + _pblock_assert_pb_intop(pblock); +- pblock->pb_intop->pb_txn_ruv_mods_fn = (IFP)value; ++ pblock->pb_intop->pb_txn_ruv_mods_fn = value; + break; + + /* Search results set */ +@@ -4027,23 +4030,23 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + break; + + case SLAPI_PLUGIN_ACL_SYNTAX_CHECK: +- pblock->pb_plugin->plg_acl_syntax_check = (IFP)value; ++ pblock->pb_plugin->plg_acl_syntax_check = value; + break; + case SLAPI_PLUGIN_ACL_ALLOW_ACCESS: +- pblock->pb_plugin->plg_acl_access_allowed = (IFP)value; ++ pblock->pb_plugin->plg_acl_access_allowed = value; + break; + case SLAPI_PLUGIN_ACL_MODS_ALLOWED: +- pblock->pb_plugin->plg_acl_mods_allowed = (IFP)value; ++ pblock->pb_plugin->plg_acl_mods_allowed = value; + break; + case SLAPI_PLUGIN_ACL_MODS_UPDATE: +- pblock->pb_plugin->plg_acl_mods_update = (IFP)value; ++ pblock->pb_plugin->plg_acl_mods_update = value; + break; + /* MMR Plugin */ + case SLAPI_PLUGIN_MMR_BETXN_PREOP: +- pblock->pb_plugin->plg_mmr_betxn_preop = (IFP) value; ++ pblock->pb_plugin->plg_mmr_betxn_preop = value; + break; + case SLAPI_PLUGIN_MMR_BETXN_POSTOP: +- pblock->pb_plugin->plg_mmr_betxn_postop = (IFP) value; ++ pblock->pb_plugin->plg_mmr_betxn_postop = value; + break; + + case SLAPI_CLIENT_DNS: +@@ -4106,11 +4109,11 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + + /* entry fetch store */ + case SLAPI_PLUGIN_ENTRY_FETCH_FUNC: +- pblock->pb_plugin->plg_entryfetchfunc = (IFP)value; ++ pblock->pb_plugin->plg_entryfetchfunc = value; + break; + + case SLAPI_PLUGIN_ENTRY_STORE_FUNC: +- pblock->pb_plugin->plg_entrystorefunc = (IFP)value; ++ pblock->pb_plugin->plg_entrystorefunc = value; + break; + + case SLAPI_PLUGIN_ENABLED: +diff --git a/ldap/servers/slapd/pblock_v3.h b/ldap/servers/slapd/pblock_v3.h +index ef15ee457..82dcb8ebf 100644 +--- a/ldap/servers/slapd/pblock_v3.h ++++ b/ldap/servers/slapd/pblock_v3.h +@@ -117,7 +117,7 @@ typedef struct _slapi_pblock_intop + void *op_stack_elem; + + void *pb_txn; /* transaction ID */ +- IFP pb_txn_ruv_mods_fn; /* Function to fetch RUV mods for txn */ ++ int32_t (*pb_txn_ruv_mods_fn)(Slapi_PBlock *, char **, Slapi_Mods **); /* Function to fetch RUV mods for txn */ + passwdPolicy *pwdpolicy; + LDAPControl **pb_ctrls_arg; /* allows to pass controls as arguments before + operation object is created */ +diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c +index 1528d5593..52d25e19e 100644 +--- a/ldap/servers/slapd/plugin.c ++++ b/ldap/servers/slapd/plugin.c +@@ -663,7 +663,7 @@ slapi_send_ldap_intermediate(Slapi_PBlock *pb, LDAPControl **ectrls, char *respo + int + slapi_send_ldap_search_entry(Slapi_PBlock *pb, Slapi_Entry *e, LDAPControl **ectrls, char **attrs, int attrsonly) + { +- IFP fn = NULL; ++ int32_t (*fn)(Slapi_PBlock *, Slapi_Entry *, LDAPControl **, char **, int32_t) = NULL; + slapi_pblock_get(pb, SLAPI_PLUGIN_DB_ENTRY_FN, (void *)&fn); + if (NULL == fn) { + return -1; +@@ -698,7 +698,7 @@ slapi_send_ldap_result_from_pb(Slapi_PBlock *pb) + int err; + char *matched; + char *text; +- IFP fn = NULL; ++ int32_t (*fn)(Slapi_PBlock*, int32_t, char*, char*, int32_t, struct berval **) = NULL; + + slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err); + slapi_pblock_get(pb, SLAPI_RESULT_TEXT, &text); +@@ -713,7 +713,7 @@ slapi_send_ldap_result_from_pb(Slapi_PBlock *pb) + void + slapi_send_ldap_result(Slapi_PBlock *pb, int err, char *matched, char *text, int nentries, struct berval **urls) + { +- IFP fn = NULL; ++ int32_t (*fn)(Slapi_PBlock*, int32_t, char*, char*, int32_t, struct berval **) = NULL; + Slapi_Operation *operation; + long op_type; + +@@ -751,7 +751,7 @@ slapi_send_ldap_result(Slapi_PBlock *pb, int err, char *matched, char *text, int + int + slapi_send_ldap_referral(Slapi_PBlock *pb, Slapi_Entry *e, struct berval **refs, struct berval ***urls) + { +- IFP fn = NULL; ++ int32_t (*fn)(Slapi_PBlock*, Slapi_Entry*, struct berval **, struct berval ***) = NULL; + slapi_pblock_get(pb, SLAPI_PLUGIN_DB_REFERRAL_FN, (void *)&fn); + if (NULL == fn) { + return -1; +@@ -1964,7 +1964,7 @@ plugin_call_func(struct slapdplugin *list, int operation, Slapi_PBlock *pb, int + int count = 0; + + for (; list != NULL; list = list->plg_next) { +- IFP func = NULL; ++ int32_t (*func)(Slapi_PBlock *) = NULL; + + slapi_pblock_set(pb, SLAPI_PLUGIN, list); + set_db_default_result_handlers(pb); /* JCM: What's this do? Is it needed here? */ +diff --git a/ldap/servers/slapd/plugin_mmr.c b/ldap/servers/slapd/plugin_mmr.c +index f58120543..845e8f0ad 100644 +--- a/ldap/servers/slapd/plugin_mmr.c ++++ b/ldap/servers/slapd/plugin_mmr.c +@@ -1,10 +1,10 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2005 Red Hat, Inc. ++ * Copyright (C) 2005-2025 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +- * See LICENSE for details. ++ * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + + #ifdef HAVE_CONFIG_H +@@ -19,11 +19,11 @@ + #include "slap.h" + + int +-plugin_call_mmr_plugin_preop ( Slapi_PBlock *pb, Slapi_Entry *e, int flags) ++plugin_call_mmr_plugin_preop(Slapi_PBlock *pb, Slapi_Entry *e, int flags) + { +- struct slapdplugin *p; +- int rc = LDAP_INSUFFICIENT_ACCESS; +- Operation *operation; ++ struct slapdplugin *p; ++ int rc = LDAP_INSUFFICIENT_ACCESS; ++ Operation *operation; + + slapi_pblock_get (pb, SLAPI_OPERATION, &operation); + +@@ -45,11 +45,11 @@ plugin_call_mmr_plugin_preop ( Slapi_PBlock *pb, Slapi_Entry *e, int flags) + } + + int +-plugin_call_mmr_plugin_postop ( Slapi_PBlock *pb, Slapi_Entry *e, int flags) ++plugin_call_mmr_plugin_postop(Slapi_PBlock *pb, Slapi_Entry *e, int flags) + { +- struct slapdplugin *p; +- int rc = LDAP_INSUFFICIENT_ACCESS; +- Operation *operation; ++ struct slapdplugin *p; ++ int rc = LDAP_INSUFFICIENT_ACCESS; ++ Operation *operation; + + slapi_pblock_get (pb, SLAPI_OPERATION, &operation); + +diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c +index 67051a5ff..757355dbc 100644 +--- a/ldap/servers/slapd/plugin_mr.c ++++ b/ldap/servers/slapd/plugin_mr.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) 2005-2025 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -35,7 +35,7 @@ struct mr_private + const struct berval *value; /* orig value from filter */ + int ftype; /* filter type */ + int op; /* query op type */ +- IFP match_fn; /* match func to use */ ++ int32_t (*match_fn)(Slapi_PBlock *, const struct berval *, Slapi_Value **, int32_t, Slapi_Value **); /* match func to use */ + /* note - substring matching rules not currently supported */ + char *initial; /* these are for substring matches */ + char *any[2]; /* at most one value for extensible filter */ +@@ -225,7 +225,7 @@ int /* an LDAP error code, hopefully LDAP_SUCCESS */ + int rc; + char *oid; + if (!(rc = slapi_pblock_get(opb, SLAPI_PLUGIN_MR_OID, &oid))) { +- IFP createFn = NULL; ++ int32_t (*createFn)(Slapi_PBlock *) = NULL; + struct slapdplugin *mrp = plugin_mr_find_registered(oid); + if (mrp != NULL) { + /* Great the matching OID -> MR plugin was already found, just reuse it */ +@@ -251,7 +251,6 @@ int /* an LDAP error code, hopefully LDAP_SUCCESS */ + rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION; + + for (mrp = get_plugin_list(PLUGIN_LIST_MATCHINGRULE); mrp != NULL; mrp = mrp->plg_next) { +- + Slapi_PBlock *pb = slapi_pblock_new(); + mr_indexer_init_pb(opb, pb); + slapi_pblock_set(pb, SLAPI_PLUGIN, mrp); +@@ -263,8 +262,8 @@ int /* an LDAP error code, hopefully LDAP_SUCCESS */ + } + + if (createFn && !createFn(pb)) { +- IFP indexFn = NULL; +- IFP indexSvFn = NULL; ++ int32_t (*indexFn)(void) = NULL; ++ int32_t (*indexSvFn)(void) = NULL; + /* These however, are in the pblock direct, so we need to copy them. */ + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_FN, &indexFn); + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_SV_FN, &indexSvFn); +@@ -624,7 +623,7 @@ static int + attempt_mr_filter_create(mr_filter_t *f, struct slapdplugin *mrp, Slapi_PBlock *pb) + { + int rc; +- IFP mrf_create = NULL; ++ int32_t (*mrf_create)(Slapi_PBlock *) = NULL; + f->mrf_match = NULL; + slapi_pblock_init(pb); + if (!(rc = slapi_pblock_set(pb, SLAPI_PLUGIN, mrp)) && +diff --git a/ldap/servers/slapd/plugin_syntax.c b/ldap/servers/slapd/plugin_syntax.c +index f8a133f96..68f7bcb4b 100644 +--- a/ldap/servers/slapd/plugin_syntax.c ++++ b/ldap/servers/slapd/plugin_syntax.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) 2005-2025 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -95,7 +95,7 @@ plugin_call_syntax_filter_ava_sv( + int useDeletedValues) + { + int rc; +- IFP ava_fn = NULL; ++ int32_t (*ava_fn)(Slapi_PBlock *, const struct berval *, Slapi_Value **, int32_t, Slapi_Value **) = NULL; + + slapi_log_err(SLAPI_LOG_FILTER, + "plugin_call_syntax_filter_ava_sv", "=> %s=%s\n", ava->ava_type, +@@ -207,7 +207,7 @@ plugin_call_syntax_filter_sub_sv( + struct subfilt *fsub) + { + int rc; +- IFP sub_fn = NULL; ++ int32_t (*sub_fn)(Slapi_PBlock *, char *, char **, char*, Slapi_Value **) = NULL; + int filter_normalized = 0; + + slapi_log_err(SLAPI_LOG_FILTER, +@@ -396,7 +396,7 @@ slapi_entry_syntax_check( + /* iterate through each value to check if it's valid */ + while (val != NULL) { + bval = slapi_value_get_berval(val); +- if ((a->a_plugin->plg_syntax_validate(bval)) != 0) { ++ if ((a->a_plugin->plg_syntax_validate((struct berval *)bval)) != 0) { + if (syntaxlogging) { + slapi_log_err(SLAPI_LOG_ERR, "slapi_entry_syntax_check", + "\"%s\": (%s) value #%d invalid per syntax\n", +@@ -586,7 +586,7 @@ slapi_attr_values2keys_sv_pb( + { + int rc; + struct slapdplugin *pi = NULL; +- IFP v2k_fn = NULL; ++ int32_t (*v2k_fn)(Slapi_PBlock*, Slapi_Value**, Slapi_Value***, int32_t) = NULL; + + if ((sattr->a_plugin == NULL)) { + /* could be lazy plugin initialization, get it now */ +@@ -748,7 +748,7 @@ slapi_attr_assertion2keys_ava_sv( + { + int rc; + struct slapdplugin *pi = NULL; +- IFP a2k_fn = NULL; ++ int32_t (*a2k_fn)(Slapi_PBlock *, Slapi_Value *, Slapi_Value ***, int32_t) = NULL; + + slapi_log_err(SLAPI_LOG_FILTER, + "slapi_attr_assertion2keys_ava_sv", "=>\n"); +@@ -880,7 +880,7 @@ slapi_attr_assertion2keys_sub_sv_pb( + Slapi_PBlock *work_pb = NULL; + struct slapdplugin *pi = NULL; + struct slapdplugin *origpi = NULL; +- IFP a2k_fn = NULL; ++ int32_t (*a2k_fn)(Slapi_PBlock *, char *, char **, char *, Slapi_Value ***) = NULL; + + slapi_log_err(SLAPI_LOG_FILTER, + "slapi_attr_assertion2keys_sub_sv_pb", "=>\n"); +@@ -951,7 +951,7 @@ slapi_attr_value_normalize_ext( + unsigned long filter_type) + { + Slapi_Attr myattr = {0}; +- VFPV norm_fn = NULL; ++ void (*norm_fn)(Slapi_PBlock *, char *, int32_t, char **) = NULL; + + if (!sattr) { + sattr = slapi_attr_init(&myattr, type); +diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c +index 65e491590..cda1c404f 100644 +--- a/ldap/servers/slapd/pw.c ++++ b/ldap/servers/slapd/pw.c +@@ -3243,7 +3243,7 @@ slapi_pw_set_entry_ext(Slapi_Entry *entry, Slapi_Value **vals, int flags) + } + + int +-pw_copy_entry_ext(Slapi_Entry *src_e, Slapi_Entry *dest_e) ++pw_copy_entry_ext(const Slapi_Entry *src_e, Slapi_Entry *dest_e) + { + struct slapi_pw_entry_ext *src_extp = NULL; + struct slapi_pw_entry_ext *dest_extp = NULL; +@@ -3254,7 +3254,7 @@ pw_copy_entry_ext(Slapi_Entry *src_e, Slapi_Entry *dest_e) + + src_extp = (struct slapi_pw_entry_ext *)slapi_get_object_extension( + pw_entry_objtype, +- src_e, ++ (void *)src_e, + pw_entry_handle); + if (NULL == src_extp) { + return LDAP_NO_SUCH_ATTRIBUTE; +diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h +index f2395cfa2..fee5a6ab5 100644 +--- a/ldap/servers/slapd/slap.h ++++ b/ldap/servers/slapd/slap.h +@@ -1,7 +1,7 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. ++ * Copyright (C) 2009-2025 Red Hat, Inc. + * All rights reserved. + * + * Contributors: +@@ -870,10 +870,10 @@ struct slapi_entry + struct attrs_in_extension + { + char *ext_type; +- IFP ext_get; +- IFP ext_set; +- IFP ext_copy; +- IFP ext_get_size; ++ int32_t (*ext_get)(Slapi_Entry *, Slapi_Value ***); ++ int32_t (*ext_set)(Slapi_Entry *, Slapi_Value **, int32_t); ++ int32_t (*ext_copy)(const Slapi_Entry *, Slapi_Entry *); ++ int32_t (*ext_get_size)(Slapi_Entry *, size_t *); + }; + + extern struct attrs_in_extension attrs_in_extension[]; +@@ -1045,7 +1045,7 @@ struct slapdplugin + int plg_precedence; /* for plugin execution ordering */ + struct slapdplugin *plg_group; /* pointer to the group to which this plugin belongs */ + struct pluginconfig plg_conf; /* plugin configuration parameters */ +- IFP plg_cleanup; /* cleanup function */ ++ int32_t (*plg_cleanup)(Slapi_PBlock *); /* cleanup function */ + IFP plg_start; /* start function */ + IFP plg_poststart; /* poststart function */ + int plg_closed; /* mark plugin as closed */ +@@ -1055,47 +1055,47 @@ struct slapdplugin + Slapi_Counter *plg_op_counter; /* operation counter, used for shutdown */ + + /* NOTE: These LDIF2DB and DB2LDIF fn pointers are internal only for now. +- I don't believe you can get these functions from a plug-in and +- then call them without knowing what IFP or VFP0 are. (These aren't +- declared in slapi-plugin.h.) More importantly, it's a pretty ugly +- way to get to these functions. (Do we want people to get locked into +- this?) +- +- The correct way to do this would be to expose these functions as +- front-end API functions. We can fix this for the next release. +- (No one has the time right now.) +- */ ++ * I don't believe you can get these functions from a plug-in and ++ * then call them without knowing what IFP or VFP0 are. (These aren't ++ * declared in slapi-plugin.h.) More importantly, it's a pretty ugly ++ * way to get to these functions. (Do we want people to get locked into ++ * this?) ++ * ++ * The correct way to do this would be to expose these functions as ++ * front-end API functions. We can fix this for the next release. ++ * (No one has the time right now.) ++ */ + union + { /* backend database plugin structure */ + struct plg_un_database_backend + { +- IFP plg_un_db_bind; /* bind */ +- IFP plg_un_db_unbind; /* unbind */ +- IFP plg_un_db_search; /* search */ +- IFP plg_un_db_next_search_entry; /* iterate */ ++ int32_t (*plg_un_db_bind)(Slapi_PBlock *); /* bind */ ++ int32_t (*plg_un_db_unbind)(Slapi_PBlock *); /* undbind */ ++ int32_t (*plg_un_db_search)(Slapi_PBlock *); /* search */ ++ int32_t (*plg_un_db_next_search_entry)(Slapi_PBlock *); /* iterate */ + IFP plg_un_db_next_search_entry_ext; +- VFPP plg_un_db_search_results_release; /* PAGED RESULTS */ +- VFP plg_un_db_prev_search_results; /* PAGED RESULTS */ +- IFP plg_un_db_entry_release; +- IFP plg_un_db_compare; /* compare */ +- IFP plg_un_db_modify; /* modify */ +- IFP plg_un_db_modrdn; /* modrdn */ +- IFP plg_un_db_add; /* add */ +- IFP plg_un_db_delete; /* delete */ +- IFP plg_un_db_abandon; /* abandon */ +- IFP plg_un_db_config; /* config */ +- IFP plg_un_db_seq; /* sequence */ +- IFP plg_un_db_entry; /* entry send */ +- IFP plg_un_db_referral; /* referral send */ +- IFP plg_un_db_result; /* result send */ +- IFP plg_un_db_ldif2db; /* ldif 2 database */ +- IFP plg_un_db_db2ldif; /* database 2 ldif */ +- IFP plg_un_db_db2index; /* database 2 index */ +- IFP plg_un_db_dbcompact; /* compact database */ +- IFP plg_un_db_archive2db; /* ldif 2 database */ +- IFP plg_un_db_db2archive; /* database 2 ldif */ +- IFP plg_un_db_upgradedb; /* convert old idl to new */ +- IFP plg_un_db_upgradednformat; /* convert old dn format to new */ ++ VFPP plg_un_db_search_results_release; /* Paged results */ ++ VFP plg_un_db_prev_search_results; /* Paged results */ ++ int32_t (*plg_un_db_entry_release)(Slapi_PBlock *, void *); /* Releas entry from cache */ ++ int32_t (*plg_un_db_compare)(Slapi_PBlock *); /* compare */ ++ int32_t (*plg_un_db_modify)(Slapi_PBlock *); /* modify */ ++ int32_t (*plg_un_db_modrdn)(Slapi_PBlock *); /* modrdn */ ++ int32_t (*plg_un_db_add)(Slapi_PBlock *); /* add */ ++ int32_t (*plg_un_db_delete)(Slapi_PBlock *); /* delete */ ++ int32_t (*plg_un_db_abandon)(Slapi_PBlock *); /* abandon */ ++ IFP plg_un_db_config; /* config */ ++ int32_t (*plg_un_db_seq)(Slapi_PBlock *); /* sequence */ ++ IFP plg_un_db_entry; /* entry send */ ++ IFP plg_un_db_referral; /* referral send */ ++ IFP plg_un_db_result; ++ int32_t (*plg_un_db_ldif2db)(Slapi_PBlock *); /* ldif 2 database */ ++ int32_t (*plg_un_db_db2ldif)(Slapi_PBlock *); /* database 2 ldif */ ++ int32_t (*plg_un_db_db2index)(Slapi_PBlock *); /* database 2 index */ ++ int32_t (*plg_un_db_dbcompact)(Slapi_Backend *, bool); /* compact database */ ++ int32_t (*plg_un_db_archive2db)(Slapi_PBlock *); /* ldif 2 database */ ++ int32_t (*plg_un_db_db2archive)(Slapi_PBlock *); /* database 2 ldif */ ++ int32_t (*plg_un_db_upgradedb)(Slapi_PBlock *); /* convert old idl to new */ ++ int32_t (*plg_un_db_upgradednformat)(Slapi_PBlock *); /* convert old dn format to new */ + IFP plg_un_db_begin; /* dbase txn begin */ + IFP plg_un_db_commit; /* dbase txn commit */ + IFP plg_un_db_abort; /* dbase txn abort */ +@@ -1105,12 +1105,12 @@ struct slapdplugin + IFP plg_un_db_register_dn_callback; /* Register a function to call when a operation is applied to a given DN */ + IFP plg_un_db_register_oc_callback; /* Register a function to call when a operation is applied to a given ObjectClass */ + IFP plg_un_db_init_instance; /* initializes new db instance */ +- IFP plg_un_db_wire_import; /* fast replica update */ +- IFP plg_un_db_verify; /* verify db files */ +- IFP plg_un_db_add_schema; /* add schema */ +- IFP plg_un_db_get_info; /* get info */ +- IFP plg_un_db_set_info; /* set info */ +- IFP plg_un_db_ctrl_info; /* ctrl info */ ++ int32_t (*plg_un_db_wire_import)(Slapi_PBlock *); /* fast replica update */ ++ int32_t (*plg_un_db_verify)(Slapi_PBlock *); /* verify db files */ ++ IFP plg_un_db_add_schema; /* add schema */ ++ int32_t (*plg_un_db_get_info)(Slapi_Backend *, int32_t, void **); /* get info */ ++ int32_t (*plg_un_db_set_info)(Slapi_Backend *, int32_t, void **); /* set info */ ++ int32_t (*plg_un_db_ctrl_info)(Slapi_Backend *, int32_t, void **); /* ctrl info */ + } plg_un_db; + #define plg_bind plg_un.plg_un_db.plg_un_db_bind + #define plg_unbind plg_un.plg_un_db.plg_un_db_unbind +@@ -1155,10 +1155,10 @@ struct slapdplugin + { + char **plg_un_pe_exoids; /* exop oids */ + char **plg_un_pe_exnames; /* exop names (may be NULL) */ +- IFP plg_un_pe_exhandler; /* handler */ ++ int32_t (*plg_un_pe_exhandler)(Slapi_PBlock *); /* handler */ + IFP plg_un_pe_pre_exhandler; /* pre extop */ + IFP plg_un_pe_post_exhandler; /* post extop */ +- IFP plg_un_pe_be_exhandler; /* handler to retrieve the be name for the operation */ ++ int32_t (*plg_un_pe_be_exhandler)(Slapi_PBlock *, Slapi_Backend **); /* handler to retrieve the be name for the operation */ + } plg_un_pe; + #define plg_exoids plg_un.plg_un_pe.plg_un_pe_exoids + #define plg_exnames plg_un.plg_un_pe.plg_un_pe_exnames +@@ -1294,20 +1294,20 @@ struct slapdplugin + /* matching rule plugin structure */ + struct plg_un_matching_rule + { +- IFP plg_un_mr_filter_create; /* factory function */ +- IFP plg_un_mr_indexer_create; /* factory function */ ++ int32_t (*plg_un_mr_filter_create)(Slapi_PBlock *); /* factory function */ ++ int32_t (*plg_un_mr_indexer_create)(Slapi_PBlock *); /* factory function */ + /* new style syntax plugin functions */ + /* not all functions will apply to all matching rule types */ + /* e.g. a SUBSTR rule will not have a filter_ava func */ +- IFP plg_un_mr_filter_ava; +- IFP plg_un_mr_filter_sub; +- IFP plg_un_mr_values2keys; +- IFP plg_un_mr_assertion2keys_ava; +- IFP plg_un_mr_assertion2keys_sub; ++ int32_t (*plg_un_mr_filter_ava)(Slapi_PBlock *, const struct berval *, Slapi_Value **, int32_t, Slapi_Value **); ++ int32_t (*plg_un_mr_filter_sub)(Slapi_PBlock *, char *, char **, char*, Slapi_Value **); ++ int32_t (*plg_un_mr_values2keys)(Slapi_PBlock *, Slapi_Value **, Slapi_Value ***, int32_t); ++ int32_t (*plg_un_mr_assertion2keys_ava)(Slapi_PBlock *, Slapi_Value *, Slapi_Value ***, int32_t); ++ int32_t (*plg_un_mr_assertion2keys_sub)(Slapi_PBlock *, char *, char **, char *, Slapi_Value ***); + int plg_un_mr_flags; + char **plg_un_mr_names; +- IFP plg_un_mr_compare; /* only for ORDERING */ +- VFPV plg_un_mr_normalize; ++ int32_t (*plg_un_mr_compare)(struct berval *, struct berval *); /* only for ORDERING */ ++ void (*plg_un_mr_normalize)(Slapi_PBlock *, char *, int32_t, char **); + } plg_un_mr; + #define plg_mr_filter_create plg_un.plg_un_mr.plg_un_mr_filter_create + #define plg_mr_indexer_create plg_un.plg_un_mr.plg_un_mr_indexer_create +@@ -1324,25 +1324,25 @@ struct slapdplugin + /* syntax plugin structure */ + struct plg_un_syntax_struct + { +- IFP plg_un_syntax_filter_ava; ++ int32_t (*plg_un_syntax_filter_ava)(Slapi_PBlock *, const struct berval *, Slapi_Value **, int32_t, Slapi_Value **); + IFP plg_un_syntax_filter_ava_sv; +- IFP plg_un_syntax_filter_sub; ++ int32_t (*plg_un_syntax_filter_sub)(Slapi_PBlock*, char *, char **, char *, Slapi_Value**); + IFP plg_un_syntax_filter_sub_sv; +- IFP plg_un_syntax_values2keys; ++ int32_t (*plg_un_syntax_values2keys)(Slapi_PBlock*, Slapi_Value**, Slapi_Value***, int32_t); + IFP plg_un_syntax_values2keys_sv; +- IFP plg_un_syntax_assertion2keys_ava; +- IFP plg_un_syntax_assertion2keys_sub; ++ int32_t (*plg_un_syntax_assertion2keys_ava)(Slapi_PBlock*, Slapi_Value*, Slapi_Value***, int32_t); ++ int32_t (*plg_un_syntax_assertion2keys_sub)(Slapi_PBlock*, char*, char**, char*, Slapi_Value***); + int plg_un_syntax_flags; + /* +- from slapi-plugin.h +-#define SLAPI_PLUGIN_SYNTAX_FLAG_ORKEYS 1 +-#define SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING 2 +-*/ ++ * from slapi-plugin.h ++#define SLAPI_PLUGIN_SYNTAX_FLAG_ORKEYS 1 ++#define SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING 2 ++ */ + char **plg_un_syntax_names; + char *plg_un_syntax_oid; + IFP plg_un_syntax_compare; +- IFP plg_un_syntax_validate; +- VFPV plg_un_syntax_normalize; ++ int32_t (*plg_un_syntax_validate)(struct berval *); ++ void (*plg_un_syntax_normalize)(Slapi_PBlock *, char *, int32_t, char **); + } plg_un_syntax; + #define plg_syntax_filter_ava plg_un.plg_un_syntax.plg_un_syntax_filter_ava + #define plg_syntax_filter_sub plg_un.plg_un_syntax.plg_un_syntax_filter_sub +@@ -1359,10 +1359,11 @@ struct slapdplugin + struct plg_un_acl_struct + { + IFP plg_un_acl_init; +- IFP plg_un_acl_syntax_check; +- IFP plg_un_acl_access_allowed; +- IFP plg_un_acl_mods_allowed; +- IFP plg_un_acl_mods_update; ++ int32_t (*plg_un_acl_syntax_check)(Slapi_PBlock *, Slapi_Entry *, char **); ++ int32_t (*plg_un_acl_access_allowed)(Slapi_PBlock *, Slapi_Entry*, char **, struct berval *, int32_t, int32_t, char **); ++ int32_t (*plg_un_acl_mods_allowed)(Slapi_PBlock *, Slapi_Entry *, LDAPMod **, void *); ++ int32_t (*plg_un_acl_mods_update)(Slapi_PBlock *, int32_t, Slapi_DN *, void *); ++ + } plg_un_acl; + #define plg_acl_init plg_un.plg_un_acl.plg_un_acl_init + #define plg_acl_syntax_check plg_un.plg_un_acl.plg_un_acl_syntax_check +@@ -1372,8 +1373,8 @@ struct slapdplugin + + struct plg_un_mmr_struct + { +- IFP plg_un_mmr_betxn_preop; +- IFP plg_un_mmr_betxn_postop; ++ int32_t (*plg_un_mmr_betxn_preop)(Slapi_PBlock *, int32_t); ++ int32_t (*plg_un_mmr_betxn_postop)(Slapi_PBlock *, int32_t); + } plg_un_mmr; + #define plg_mmr_betxn_preop plg_un.plg_un_mmr.plg_un_mmr_betxn_preop + #define plg_mmr_betxn_postop plg_un.plg_un_mmr.plg_un_mmr_betxn_postop +@@ -1394,8 +1395,8 @@ struct slapdplugin + /* entry fetch/store */ + struct plg_un_entry_fetch_store_struct + { +- IFP plg_un_entry_fetch_func; +- IFP plg_un_entry_store_func; ++ int32_t (*plg_un_entry_fetch_func)(char **, uint32_t *); ++ int32_t (*plg_un_entry_store_func)(char **, uint32_t *); + } plg_un_entry_fetch_store; + #define plg_entryfetchfunc plg_un.plg_un_entry_fetch_store.plg_un_entry_fetch_func + #define plg_entrystorefunc plg_un.plg_un_entry_fetch_store.plg_un_entry_store_func +diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h +index ec4b540c3..533c4e428 100644 +--- a/ldap/servers/slapd/slapi-private.h ++++ b/ldap/servers/slapd/slapi-private.h +@@ -1409,7 +1409,7 @@ int slapi_add_internal_attr_syntax(const char *name, const char *oid, const char + + /* pw.c */ + void pw_exp_init(void); +-int pw_copy_entry_ext(Slapi_Entry *src_e, Slapi_Entry *dest_e); ++int pw_copy_entry_ext(const Slapi_Entry *src_e, Slapi_Entry *dest_e); + int pw_get_ext_size(Slapi_Entry *e, size_t *size); + + /* op_shared.c */ +diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c +index c14af83e0..d2a9a1b48 100644 +--- a/ldap/servers/slapd/task.c ++++ b/ldap/servers/slapd/task.c +@@ -2036,7 +2036,7 @@ task_upgradedb_add(Slapi_PBlock *pb __attribute__((unused)), + int32_t task_flags = SLAPI_TASK_RUNNING_AS_TASK; + slapi_pblock_set(mypb, SLAPI_TASK_FLAGS, &task_flags); + +- rv = (be->be_database->plg_upgradedb)(&mypb); ++ rv = (be->be_database->plg_upgradedb)(mypb); + if (rv == 0) { + slapi_entry_attr_set_charptr(e, TASK_LOG_NAME, ""); + slapi_entry_attr_set_charptr(e, TASK_STATUS_NAME, ""); +-- +2.52.0 + diff --git a/SOURCES/0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch b/SOURCES/0011-Issue-6872-compressed-log-rotation-creates-files-wit.patch deleted file mode 100644 index cc717a3..0000000 --- a/SOURCES/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/SOURCES/0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch b/SOURCES/0012-Issue-6878-Prevent-repeated-disconnect-logs-during-s.patch deleted file mode 100644 index 8dbfc90..0000000 --- a/SOURCES/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/SOURCES/0012-Issue-7223-Revert-index-scan-limits-for-system-index.patch b/SOURCES/0012-Issue-7223-Revert-index-scan-limits-for-system-index.patch new file mode 100644 index 0000000..c3fde35 --- /dev/null +++ b/SOURCES/0012-Issue-7223-Revert-index-scan-limits-for-system-index.patch @@ -0,0 +1,785 @@ +From 7a45b50bf3d9cfc8f7e805379fcb39d5669d6c8b Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Thu, 5 Feb 2026 12:17:06 +0100 +Subject: [PATCH] Issue 7223 - Revert index scan limits for system indexes + +This reverts changes introduced by the following commits: +c6f458b42 Issue 7189 - DSBLE0007 generates incorrect remediation commands for scan limits +8b6b3a9f9 Issue 6966 - On large DB, unlimited IDL scan limit reduce the SRCH performance + +Relates: https://github.com/389ds/389-ds-base/issues/7223 + +Reviewed by: @progier389, @tbordaz, @droideck (Thanks!) +--- + .../tests/suites/config/config_test.py | 27 +--- + .../healthcheck/health_system_indexes_test.py | 135 +----------------- + .../paged_results/paged_results_test.py | 25 +--- + ldap/servers/slapd/back-ldbm/back-ldbm.h | 1 - + ldap/servers/slapd/back-ldbm/index.c | 2 - + ldap/servers/slapd/back-ldbm/instance.c | 106 +++----------- + ldap/servers/slapd/back-ldbm/ldbm_config.c | 30 ---- + ldap/servers/slapd/back-ldbm/ldbm_config.h | 1 - + .../slapd/back-ldbm/ldbm_index_config.c | 8 -- + src/lib389/lib389/backend.py | 50 ++----- + src/lib389/lib389/cli_conf/backend.py | 20 --- + 11 files changed, 41 insertions(+), 364 deletions(-) + +diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py +index b9ba684d9..6f11e7fc8 100644 +--- a/dirsrvtests/tests/suites/config/config_test.py ++++ b/dirsrvtests/tests/suites/config/config_test.py +@@ -715,19 +715,17 @@ def test_ndn_cache_size_enforcement(topo, request): + + request.addfinalizer(fin) + +-def test_require_index(topo, request): ++def test_require_index(topo): + """Validate that unindexed searches are rejected + + :id: fb6e31f2-acc2-4e75-a195-5c356faeb803 + :setup: Standalone instance + :steps: + 1. Set "nsslapd-require-index" to "on" +- 2. ancestorid/idlscanlimit to 100 +- 3. Test an unindexed search is rejected ++ 2. Test an unindexed search is rejected + :expectedresults: + 1. Success + 2. Success +- 3. Success + """ + + # Set the config +@@ -738,10 +736,6 @@ def test_require_index(topo, request): + + db_cfg = DatabaseConfig(topo.standalone) + db_cfg.set([('nsslapd-idlistscanlimit', '100')]) +- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX) +- ancestorid_index = backend.get_index('ancestorid') +- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND")) +- topo.standalone.restart() + + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + for i in range(101): +@@ -752,15 +746,10 @@ def test_require_index(topo, request): + with pytest.raises(ldap.UNWILLING_TO_PERFORM): + raw_objects.filter("(description=test*)") + +- def fin(): +- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND")) +- +- request.addfinalizer(fin) +- + + + @pytest.mark.skipif(ds_is_older('1.4.2'), reason="The config setting only exists in 1.4.2 and higher") +-def test_require_internal_index(topo, request): ++def test_require_internal_index(topo): + """Ensure internal operations require indexed attributes + + :id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f +@@ -791,10 +780,6 @@ def test_require_internal_index(topo, request): + # Create a bunch of users + db_cfg = DatabaseConfig(topo.standalone) + db_cfg.set([('nsslapd-idlistscanlimit', '100')]) +- backend = Backends(topo.standalone).get_backend(DEFAULT_SUFFIX) +- ancestorid_index = backend.get_index('ancestorid') +- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND")) +- topo.standalone.restart() + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + for i in range(102, 202): + users.create_test_user(uid=i) +@@ -819,12 +804,6 @@ def test_require_internal_index(topo, request): + with pytest.raises(ldap.UNWILLING_TO_PERFORM): + user.delete() + +- def fin(): +- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND")) +- +- request.addfinalizer(fin) +- +- + + def get_pstack(pid): + """Get a pstack of the pid.""" +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 5eadf6283..61972d60c 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -171,8 +171,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled): + + log.info("Re-add the parentId index") + backend = Backends(standalone).get("userRoot") +- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"], +- idlistscanlimit=['limit=5000 type=eq flags=AND']) ++ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"]) + + run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +@@ -260,8 +259,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer + + log.info("Re-add the entryusn index") + backend = Backends(standalone).get("userRoot") +- backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"], +- idlistscanlimit=['limit=5000 type=eq flags=AND']) ++ backend.add_index("entryusn", ["eq"], matching_rules=["integerOrderingMatch"]) + + run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +@@ -405,132 +403,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) + + +-def test_missing_scanlimit(topology_st, log_buffering_enabled): +- """Check if healthcheck returns DSBLE0007 code when parentId index is missing scanlimit +- +- :id: 40e1bf6a-2397-459b-bdf3-f787ca118b86 +- :setup: Standalone instance +- :steps: +- 1. Create DS instance +- 2. Remove nsIndexIDListScanLimit from parentId index +- 3. Use healthcheck without --json option +- 4. Use healthcheck with --json option +- 5. Verify the remediation command has properly quoted scanlimit +- 6. Re-add the scanlimit +- 7. Use healthcheck without --json option +- 8. Use healthcheck with --json option +- :expectedresults: +- 1. Success +- 2. Success +- 3. healthcheck reports DSBLE0007 code and related details +- 4. healthcheck reports DSBLE0007 code and related details +- 5. The scanlimit value is quoted in the remediation command +- 6. Success +- 7. healthcheck reports no issues found +- 8. healthcheck reports no issues found +- """ +- +- RET_CODE = "DSBLE0007" +- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" +- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND" +- +- standalone = topology_st.standalone +- +- log.info("Remove nsIndexIDListScanLimit from parentId index") +- parentid_index = Index(standalone, PARENTID_DN) +- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE) +- +- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE) +- +- # Verify the remediation command has properly quoted scanlimit +- args = FakeArgs() +- args.instance = standalone.serverid +- args.verbose = standalone.verbose +- args.list_errors = False +- args.list_checks = False +- args.exclude_check = [] +- args.check = ["backends"] +- args.dry_run = False +- args.json = False +- health_check_run(standalone, topology_st.logcap.log, args) +- # Check that the scanlimit is quoted in the output +- assert topology_st.logcap.contains('--add-scanlimit "limit=5000 type=eq flags=AND"') +- log.info("Verified scanlimit is properly quoted in remediation command") +- topology_st.logcap.flush() +- +- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE) +- +- log.info("Re-add the nsIndexIDListScanLimit") +- parentid_index = Index(standalone, PARENTID_DN) +- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE) +- +- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) +- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +- +- +-def test_missing_matching_rule_and_scanlimit(topology_st, log_buffering_enabled): +- """Check if healthcheck generates a single combined command when both matching rule and scanlimit are missing +- +- :id: af8214ad-5e4c-422a-8f74-3e99227551df +- :setup: Standalone instance +- :steps: +- 1. Create DS instance +- 2. Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index +- 3. Use healthcheck and verify a single combined command is generated +- 4. Re-add the matching rule and scanlimit +- 5. Use healthcheck without --json option +- 6. Use healthcheck with --json option +- :expectedresults: +- 1. Success +- 2. Success +- 3. healthcheck reports DSBLE0007 and generates a single command with both --add-mr and --add-scanlimit +- 4. Success +- 5. healthcheck reports no issues found +- 6. healthcheck reports no issues found +- """ +- +- RET_CODE = "DSBLE0007" +- PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" +- SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND" +- +- standalone = topology_st.standalone +- +- log.info("Remove both integerOrderingMatch and nsIndexIDListScanLimit from parentId index") +- parentid_index = Index(standalone, PARENTID_DN) +- parentid_index.remove("nsMatchingRule", "integerOrderingMatch") +- parentid_index.remove("nsIndexIDListScanLimit", SCANLIMIT_VALUE) +- +- # Run healthcheck and verify combined command +- args = FakeArgs() +- args.instance = standalone.serverid +- args.verbose = standalone.verbose +- args.list_errors = False +- args.list_checks = False +- args.exclude_check = [] +- args.check = ["backends"] +- args.dry_run = False +- args.json = False +- health_check_run(standalone, topology_st.logcap.log, args) +- +- # Verify DSBLE0007 is reported +- assert topology_st.logcap.contains(RET_CODE) +- log.info("healthcheck returned code: %s" % RET_CODE) +- +- # Verify a single combined command is generated with both --add-mr and --add-scanlimit +- assert topology_st.logcap.contains('--add-mr integerOrderingMatch --add-scanlimit "limit=5000 type=eq flags=AND"') +- log.info("Verified combined command with both --add-mr and --add-scanlimit") +- +- topology_st.logcap.flush() +- +- log.info("Re-add the integerOrderingMatch matching rule and scanlimit") +- parentid_index = Index(standalone, PARENTID_DN) +- parentid_index.add("nsMatchingRule", "integerOrderingMatch") +- parentid_index.add("nsIndexIDListScanLimit", SCANLIMIT_VALUE) +- +- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) +- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +- +- + def test_multiple_missing_indexes(topology_st, log_buffering_enabled): + """Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing + +@@ -571,8 +443,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled): + + log.info("Re-add the missing system indexes") + backend = Backends(standalone).get("userRoot") +- backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"], +- idlistscanlimit=['limit=5000 type=eq flags=AND']) ++ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"]) + backend.add_index("nsuniqueid", ["eq"]) + + run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) +diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py +index 61d6702da..1bb94b53a 100644 +--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py ++++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py +@@ -306,19 +306,19 @@ def test_search_success(topology_st, create_user, page_size, users_num): + del_users(users_list) + + +-@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err, restart", [ ++@pytest.mark.parametrize("page_size,users_num,suffix,attr_name,attr_value,expected_err", [ + (50, 200, 'cn=config,%s' % DN_LDBM, 'nsslapd-idlistscanlimit', '100', +- ldap.UNWILLING_TO_PERFORM, True), ++ ldap.UNWILLING_TO_PERFORM), + (5, 15, DN_CONFIG, 'nsslapd-timelimit', '20', +- ldap.UNAVAILABLE_CRITICAL_EXTENSION, False), ++ ldap.UNAVAILABLE_CRITICAL_EXTENSION), + (21, 50, DN_CONFIG, 'nsslapd-sizelimit', '20', +- ldap.SIZELIMIT_EXCEEDED, False), ++ ldap.SIZELIMIT_EXCEEDED), + (21, 50, DN_CONFIG, 'nsslapd-pagedsizelimit', '5', +- ldap.SIZELIMIT_EXCEEDED, False), ++ ldap.SIZELIMIT_EXCEEDED), + (5, 50, 'cn=config,%s' % DN_LDBM, 'nsslapd-lookthroughlimit', '20', +- ldap.ADMINLIMIT_EXCEEDED, False)]) ++ ldap.ADMINLIMIT_EXCEEDED)]) + def test_search_limits_fail(topology_st, create_user, page_size, users_num, +- suffix, attr_name, attr_value, expected_err, restart): ++ suffix, attr_name, attr_value, expected_err): + """Verify that search with a simple paged results control + throws expected exceptoins when corresponding limits are + exceeded. +@@ -341,15 +341,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num, + + users_list = add_users(topology_st, users_num, DEFAULT_SUFFIX) + attr_value_bck = change_conf_attr(topology_st, suffix, attr_name, attr_value) +- ancestorid_index = None +- if attr_name == 'nsslapd-idlistscanlimit': +- backend = Backends(topology_st.standalone).get_backend(DEFAULT_SUFFIX) +- ancestorid_index = backend.get_index('ancestorid') +- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=100 type=eq flags=AND")) +- +- if (restart): +- log.info('Instance restarted') +- topology_st.standalone.restart() + conf_param_dict = {attr_name: attr_value} + search_flt = r'(uid=test*)' + searchreq_attrlist = ['dn', 'sn'] +@@ -402,8 +393,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num, + else: + break + finally: +- if ancestorid_index: +- ancestorid_index.replace("nsIndexIDListScanLimit", ensure_bytes("limit=5000 type=eq flags=AND")) + del_users(users_list) + change_conf_attr(topology_st, suffix, attr_name, attr_value_bck) + +diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h +index 5e4988782..a4993208c 100644 +--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h ++++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h +@@ -561,7 +561,6 @@ struct ldbminfo + int li_mode; + int li_lookthroughlimit; + int li_allidsthreshold; +- int li_system_allidsthreshold; + char *li_directory; + int li_reslimit_lookthrough_handle; + uint64_t li_dbcachesize; +diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c +index e06a8ee5f..90129b682 100644 +--- a/ldap/servers/slapd/back-ldbm/index.c ++++ b/ldap/servers/slapd/back-ldbm/index.c +@@ -1007,8 +1007,6 @@ index_read_ext_allids( + } + if (pb) { + slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and); +- } else if (strcasecmp(type, LDBM_ANCESTORID_STR) == 0) { +- is_and = 1; + } + ai_flags = is_and ? INDEX_ALLIDS_FLAG_AND : 0; + /* the caller can pass in a value of 0 - just ignore those - but if the index +diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c +index 23bd70243..f9a546661 100644 +--- a/ldap/servers/slapd/back-ldbm/instance.c ++++ b/ldap/servers/slapd/back-ldbm/instance.c +@@ -16,7 +16,7 @@ + + /* Forward declarations */ + static void ldbm_instance_destructor(void **arg); +-Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr, char *scanlimit); ++Slapi_Entry *ldbm_instance_init_config_entry(char *cn_val, char *v1, char *v2, char *v3, char *v4, char *mr); + + + /* Creates and initializes a new ldbm_instance structure. +@@ -127,7 +127,7 @@ done: + * Take a bunch of strings, and create a index config entry + */ + Slapi_Entry * +-ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr, char *scanlimit) ++ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3, char *val4, char *mr) + { + Slapi_Entry *e = slapi_entry_alloc(); + struct berval *vals[2]; +@@ -168,11 +168,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3 + slapi_entry_add_values(e, "nsMatchingRule", vals); + } + +- if (scanlimit) { +- val.bv_val = scanlimit; +- val.bv_len = strlen(scanlimit); +- slapi_entry_add_values(e, "nsIndexIDListScanLimit", vals); +- } + return e; + } + +@@ -185,60 +180,8 @@ ldbm_instance_create_default_indexes(backend *be) + { + Slapi_Entry *e; + ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; +- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private; + /* write the dse file only on the final index */ + int flags = LDBM_INSTANCE_CONFIG_DONT_WRITE; +- char *ancestorid_indexes_limit = NULL; +- char *parentid_indexes_limit = NULL; +- struct attrinfo *ai = NULL; +- int index_already_configured = 0; +- struct index_idlistsizeinfo *iter; +- int cookie; +- int limit; +- +- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai); +- if (ai && ai->ai_idlistinfo) { +- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie); +- if (iter) { +- limit = iter->ai_idlistsizelimit; +- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes", +- "set ancestorid limit to %d from attribute index\n", +- limit); +- } else { +- limit = li->li_system_allidsthreshold; +- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes", +- "set ancestorid limit to %d from default (fail to read limit)\n", +- limit); +- } +- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit); +- } else { +- ancestorid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold); +- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes", +- "set ancestorid limit to %d from default (no attribute or limit)\n", +- li->li_system_allidsthreshold); +- } +- +- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai); +- if (ai && ai->ai_idlistinfo) { +- iter = (struct index_idlistsizeinfo *)dl_get_first(ai->ai_idlistinfo, &cookie); +- if (iter) { +- limit = iter->ai_idlistsizelimit; +- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes", +- "set parentid limit to %d from attribute index\n", +- limit); +- } else { +- limit = li->li_system_allidsthreshold; +- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes", +- "set parentid limit to %d from default (fail to read limit)\n", +- limit); +- } +- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", limit); +- } else { +- parentid_indexes_limit = slapi_ch_smprintf("limit=%d type=eq flags=AND", li->li_system_allidsthreshold); +- slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_instance_create_default_indexes", +- "set parentid limit to %d from default (no attribute or limit)\n", +- li->li_system_allidsthreshold); +- } + + /* + * Always index (entrydn or entryrdn), parentid, objectclass, +@@ -247,53 +190,47 @@ ldbm_instance_create_default_indexes(backend *be) + * ACL routines. + */ + if (entryrdn_get_switch()) { /* subtree-rename: on */ +- e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + } else { +- e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + } + +- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai); +- /* Check if the attrinfo is actually for parentid, not a fallback to .default */ +- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_PARENTID_STR) == 0); +- if (!index_already_configured) { +- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit); +- ldbm_instance_config_add_index_entry(inst, e, flags); +- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL); +- slapi_entry_free(e); +- } ++ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch"); ++ ldbm_instance_config_add_index_entry(inst, e, flags); ++ slapi_entry_free(e); + +- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + +- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + +- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + +- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + + /* For MMR, we need this attribute (to replace use of dncomp in delete). */ +- e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(ATTR_NSDS5_REPLCONFLICT, "eq", "pres", 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + + /* write the dse file only on the final index */ +- e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(SLAPI_ATTR_NSCP_ENTRYDN, "eq", 0, 0, 0, 0); + ldbm_instance_config_add_index_entry(inst, e, flags); + slapi_entry_free(e); + + /* ldbm_instance_config_add_index_entry(inst, 2, argv); */ +- e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0, 0); ++ e = ldbm_instance_init_config_entry(LDBM_PSEUDO_ATTR_DEFAULT, "none", 0, 0, 0, 0); + attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL); + slapi_entry_free(e); + +@@ -302,20 +239,11 @@ ldbm_instance_create_default_indexes(backend *be) + * ancestorid is special, there is actually no such attr type + * but we still want to use the attr index file APIs. + */ +- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai); +- /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */ +- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0); +- if (!index_already_configured) { +- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit); +- ldbm_instance_config_add_index_entry(inst, e, flags); +- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL); +- slapi_entry_free(e); +- } ++ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch"); ++ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL); ++ slapi_entry_free(e); + } + +- slapi_ch_free_string(&ancestorid_indexes_limit); +- slapi_ch_free_string(&parentid_indexes_limit); +- + return 0; + } + +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c +index d8ffc4479..5cce0dbd3 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c +@@ -386,35 +386,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu + return retval; + } + +-static void * +-ldbm_config_system_allidsthreshold_get(void *arg) +-{ +- struct ldbminfo *li = (struct ldbminfo *)arg; +- +- return (void *)((uintptr_t)(li->li_system_allidsthreshold)); +-} +- +-static int +-ldbm_config_system_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply) +-{ +- struct ldbminfo *li = (struct ldbminfo *)arg; +- int retval = LDAP_SUCCESS; +- int val = (int)((uintptr_t)value); +- +- /* Do whatever we can to make sure the data is ok. */ +- +- /* Catch attempts to configure a stupidly low ancestorid allidsthreshold */ +- if ((val > -1) && (val < 5000)) { +- val = 5000; +- } +- +- if (apply) { +- li->li_system_allidsthreshold = val; +- } +- +- return retval; +-} +- + static void * + ldbm_config_pagedallidsthreshold_get(void *arg) + { +@@ -1133,7 +1104,6 @@ static config_info ldbm_config[] = { + {CONFIG_LOOKTHROUGHLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_lookthroughlimit_get, &ldbm_config_lookthroughlimit_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_MODE, CONFIG_TYPE_INT_OCTAL, "0600", &ldbm_config_mode_get, &ldbm_config_mode_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_IDLISTSCANLIMIT, CONFIG_TYPE_INT, "2147483646", &ldbm_config_allidsthreshold_get, &ldbm_config_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, +- {CONFIG_SYSTEMIDLISTSCANLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_system_allidsthreshold_get, &ldbm_config_system_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_DIRECTORY, CONFIG_TYPE_STRING, "", &ldbm_config_directory_get, &ldbm_config_directory_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE | CONFIG_FLAG_SKIP_DEFAULT_SETTING}, + {CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &ldbm_config_maxpassbeforemerge_get, &ldbm_config_maxpassbeforemerge_set, 0}, + +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h +index 30e3477ed..e1f05d7a2 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h ++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h +@@ -60,7 +60,6 @@ struct config_info + #define CONFIG_RANGELOOKTHROUGHLIMIT "nsslapd-rangelookthroughlimit" + #define CONFIG_PAGEDLOOKTHROUGHLIMIT "nsslapd-pagedlookthroughlimit" + #define CONFIG_IDLISTSCANLIMIT "nsslapd-idlistscanlimit" +-#define CONFIG_SYSTEMIDLISTSCANLIMIT "nsslapd-systemidlistscanlimit" + #define CONFIG_PAGEDIDLISTSCANLIMIT "nsslapd-pagedidlistscanlimit" + #define CONFIG_DIRECTORY "nsslapd-directory" + #define CONFIG_MODE "nsslapd-mode" +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c +index bae2a64b9..38e7368e1 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c +@@ -384,14 +384,6 @@ ldbm_instance_config_add_index_entry( + } + } + +- /* get nsIndexIDListScanLimit and its values, and add them */ +- if (0 == slapi_entry_attr_find(e, "nsIndexIDListScanLimit", &attr)) { +- for (j = slapi_attr_first_value(attr, &sval); j != -1; j = slapi_attr_next_value(attr, j, &sval)) { +- attrValue = slapi_value_get_berval(sval); +- eBuf = PR_sprintf_append(eBuf, "nsIndexIDListScanLimit: %s\n", attrValue->bv_val); +- } +- } +- + ldbm_config_add_dse_entry(li, eBuf, flags); + if (eBuf) { + PR_smprintf_free(eBuf); +diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py +index d3c9ccf35..1d9be4683 100644 +--- a/src/lib389/lib389/backend.py ++++ b/src/lib389/lib389/backend.py +@@ -615,10 +615,11 @@ class Backend(DSLdapObject): + indexes = self.get_indexes() + + # Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c ++ # Note: entryrdn and ancestorid are internal system indexes that are not ++ # exposed in cn=config - they are managed internally by the server. ++ # Only parentid has a DSE config entry (for the integerOrderingMatch rule). + expected_system_indexes = { +- 'entryrdn': {'types': ['subtree'], 'matching_rule': None}, +- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'}, +- 'ancestorid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch', 'scanlimit': 'limit=5000 type=eq flags=AND'}, ++ 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'}, + 'objectClass': {'types': ['eq'], 'matching_rule': None}, + 'aci': {'types': ['pres'], 'matching_rule': None}, + 'nscpEntryDN': {'types': ['eq'], 'matching_rule': None}, +@@ -675,17 +676,14 @@ class Backend(DSLdapObject): + # Generate remediation command + index_types = ' '.join([f"--index-type {t}" for t in expected_config['types']]) + cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}" +- if expected_config.get('matching_rule'): ++ if expected_config['matching_rule']: + cmd += f" --matching-rule {expected_config['matching_rule']}" +- if expected_config.get('scanlimit'): +- cmd += f" --add-scanlimit \"{expected_config['scanlimit']}\"" + remediation_commands.append(cmd) + reindex_attrs.add(attr_name) # New index needs reindexing + else: + # Index exists, check configuration + actual_types = index.get_attr_vals_utf8('nsIndexType') or [] + actual_mrs = index.get_attr_vals_utf8('nsMatchingRule') or [] +- actual_scanlimit = index.get_attr_vals_utf8('nsIndexIDListScanLimit') or [] + + # Normalize to lowercase for comparison + actual_types = [t.lower() for t in actual_types] +@@ -700,31 +698,16 @@ class Backend(DSLdapObject): + remediation_commands.append(cmd) + reindex_attrs.add(attr_name) + +- # Check matching rules and scanlimit together to generate a single combined command ++ # Check matching rules + expected_mr = expected_config.get('matching_rule') +- expected_scanlimit = expected_config.get('scanlimit') +- +- missing_mr = False + if expected_mr: + actual_mrs_lower = [mr.lower() for mr in actual_mrs] + if expected_mr.lower() not in actual_mrs_lower: + discrepancies.append(f"Index {attr_name} missing matching rule: {expected_mr}") +- missing_mr = True +- +- missing_scanlimit = False +- if expected_scanlimit and (len(actual_scanlimit) == 0): +- discrepancies.append(f"Index {attr_name} missing fine grain definition of IDs limit: {expected_scanlimit}") +- missing_scanlimit = True +- +- # Generate a single combined command for all missing items +- if missing_mr or missing_scanlimit: +- cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name}" +- if missing_mr: +- cmd += f" --add-mr {expected_mr}" +- if missing_scanlimit: +- cmd += f" --add-scanlimit \"{expected_scanlimit}\"" +- remediation_commands.append(cmd) +- reindex_attrs.add(attr_name) ++ # Add the missing matching rule ++ cmd = f"dsconf YOUR_INSTANCE backend index set {bename} --attr {attr_name} --add-mr {expected_mr}" ++ remediation_commands.append(cmd) ++ reindex_attrs.add(attr_name) + + except Exception as e: + self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}") +@@ -963,13 +946,12 @@ class Backend(DSLdapObject): + return + raise ValueError("Can not delete index because it does not exist") + +- def add_index(self, attr_name, types, matching_rules=None, idlistscanlimit=None, reindex=False): ++ def add_index(self, attr_name, types, matching_rules=None, reindex=False): + """ Add an index. + + :param attr_name - name of the attribute to index + :param types - a List of index types(eq, pres, sub, approx) + :param matching_rules - a List of matching rules for the index +- :param idlistscanlimit - a List of fine grain definitions for scanning limit + :param reindex - If set to True then index the attribute after creating it. + """ + +@@ -999,15 +981,6 @@ class Backend(DSLdapObject): + # Only add if there are actually rules present in the list. + if len(mrs) > 0: + props['nsMatchingRule'] = mrs +- +- if idlistscanlimit is not None: +- scanlimits = [] +- for scanlimit in idlistscanlimit: +- scanlimits.append(scanlimit) +- # Only add if there are actually limits in the list. +- if len(scanlimits) > 0: +- props['nsIndexIDListScanLimit'] = scanlimits +- + new_index.create(properties=props, basedn="cn=index," + self._dn) + + if reindex: +@@ -1314,7 +1287,6 @@ class DatabaseConfig(DSLdapObject): + 'nsslapd-lookthroughlimit', + 'nsslapd-mode', + 'nsslapd-idlistscanlimit', +- 'nsslapd-systemidlistscanlimit', + 'nsslapd-directory', + 'nsslapd-import-cachesize', + 'nsslapd-idl-switch', +diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py +index dcf57d006..80008d22d 100644 +--- a/src/lib389/lib389/cli_conf/backend.py ++++ b/src/lib389/lib389/cli_conf/backend.py +@@ -39,7 +39,6 @@ arg_to_attr = { + 'mode': 'nsslapd-mode', + 'state': 'nsslapd-state', + 'idlistscanlimit': 'nsslapd-idlistscanlimit', +- 'systemidlistscanlimit': 'nsslapd-systemidlistscanlimit', + 'directory': 'nsslapd-directory', + 'dbcachesize': 'nsslapd-dbcachesize', + 'logdirectory': 'nsslapd-db-logdirectory', +@@ -626,21 +625,6 @@ def backend_set_index(inst, basedn, log, args): + except ldap.NO_SUCH_ATTRIBUTE: + raise ValueError('Can not delete matching rule type because it does not exist') + +- if args.replace_scanlimit is not None: +- for replace_scanlimit in args.replace_scanlimit: +- index.replace('nsIndexIDListScanLimit', replace_scanlimit) +- +- if args.add_scanlimit is not None: +- for add_scanlimit in args.add_scanlimit: +- index.add('nsIndexIDListScanLimit', add_scanlimit) +- +- if args.del_scanlimit is not None: +- for del_scanlimit in args.del_scanlimit: +- try: +- index.remove('nsIndexIDListScanLimit', del_scanlimit) +- except ldap.NO_SUCH_ATTRIBUTE: +- raise ValueError('Can not delete a fine grain limit definition because it does not exist') +- + if args.reindex: + be.reindex(attrs=[args.attr]) + log.info("Index successfully updated") +@@ -962,9 +946,6 @@ def create_parser(subparsers): + edit_index_parser.add_argument('--del-type', action='append', help='Removes an index type from the index: (eq, sub, pres, or approx)') + edit_index_parser.add_argument('--add-mr', action='append', help='Adds a matching-rule to the index') + edit_index_parser.add_argument('--del-mr', action='append', help='Removes a matching-rule from the index') +- edit_index_parser.add_argument('--add-scanlimit', action='append', help='Adds a fine grain limit definiton to the index') +- edit_index_parser.add_argument('--replace-scanlimit', action='append', help='Replaces a fine grain limit definiton to the index') +- edit_index_parser.add_argument('--del-scanlimit', action='append', help='Removes a fine grain limit definiton to the index') + edit_index_parser.add_argument('--reindex', action='store_true', help='Re-indexes the database after editing the index') + edit_index_parser.add_argument('be_name', help='The backend name or suffix') + +@@ -1091,7 +1072,6 @@ def create_parser(subparsers): + 'will check when examining candidate entries in response to a search request') + set_db_config_parser.add_argument('--mode', help='Specifies the permissions used for newly created index files') + set_db_config_parser.add_argument('--idlistscanlimit', help='Specifies the number of entry IDs that are searched during a search operation') +- set_db_config_parser.add_argument('--systemidlistscanlimit', help='Specifies the number of entry IDs that are fetch from ancestorid/parentid indexes') + set_db_config_parser.add_argument('--directory', help='Specifies absolute path to database instance') + set_db_config_parser.add_argument('--dbcachesize', help='Specifies the database index cache size in bytes') + set_db_config_parser.add_argument('--logdirectory', help='Specifies the path to the directory that contains the database transaction logs') +-- +2.52.0 + diff --git a/SOURCES/0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch b/SOURCES/0013-Issue-6772-dsconf-Replicas-with-the-consumer-role-al.patch deleted file mode 100644 index 64f23ea..0000000 --- a/SOURCES/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/SOURCES/0013-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch b/SOURCES/0013-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch new file mode 100644 index 0000000..b55d17b --- /dev/null +++ b/SOURCES/0013-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch @@ -0,0 +1,212 @@ +From a58212943da2604091e7d5a20829ab54970d41c9 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Thu, 5 Feb 2026 12:17:06 +0100 +Subject: [PATCH] Issue 7223 - Add upgrade function to remove + nsIndexIDListScanLimit from parentid + +Description: +Add `upgrade_remove_index_scanlimit()` function that removes the +nsIndexIDListScanLimit attribute from parentid index configuration +if present. + +This attribute was incorrectly added by a previous version and can +cause issues with index configuration. The upgrade function runs +automatically on server startup and removes the attribute if found. + +Relates: https://github.com/389ds/389-ds-base/issues/7223 + +Reviewed by: @progier389, @tbordaz, @droideck (Thanks!) +--- + .../healthcheck/health_system_indexes_test.py | 52 +++++++++ + ldap/servers/slapd/upgrade.c | 105 ++++++++++++++++++ + 2 files changed, 157 insertions(+) + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 61972d60c..b0d7a99ec 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -450,6 +450,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled): + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) + + ++def test_upgrade_removes_parentid_scanlimit(topology_st): ++ """Check if upgrade function removes nsIndexIDListScanLimit from parentid index ++ ++ :id: 2808886e-c1c1-441d-b3a3-299c4ef1ab4a ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Use DSEldif to add nsIndexIDListScanLimit to parentid index ++ 4. Start the server (triggers upgrade) ++ 5. Verify nsIndexIDListScanLimit is removed from parentid index ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. nsIndexIDListScanLimit is no longer present ++ """ ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" ++ SCANLIMIT_VALUE = "limit=5000 type=eq flags=AND" ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif") ++ dse_ldif = DSEldif(standalone) ++ dse_ldif.add(PARENTID_DN, "nsIndexIDListScanLimit", SCANLIMIT_VALUE) ++ ++ # Verify it was added ++ scanlimit = dse_ldif.get(PARENTID_DN, "nsIndexIDListScanLimit") ++ assert scanlimit is not None, "Failed to add nsIndexIDListScanLimit" ++ log.info(f"Added nsIndexIDListScanLimit: {scanlimit}") ++ ++ log.info("Start the server (triggers upgrade)") ++ standalone.start() ++ ++ log.info("Verify nsIndexIDListScanLimit was removed by upgrade") ++ # Check via LDAP - the upgrade should have removed it ++ parentid_index = Index(standalone, PARENTID_DN) ++ scanlimit_after = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit") ++ log.info(f"nsIndexIDListScanLimit after upgrade: {scanlimit_after}") ++ ++ # The upgrade function should have removed nsIndexIDListScanLimit ++ assert not scanlimit_after, \ ++ f"nsIndexIDListScanLimit should have been removed but found: {scanlimit_after}" ++ ++ log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index") ++ ++ + if __name__ == "__main__": + # Run isolated + # -s for DEBUG mode +diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c +index 43906c1af..adfec63de 100644 +--- a/ldap/servers/slapd/upgrade.c ++++ b/ldap/servers/slapd/upgrade.c +@@ -279,6 +279,107 @@ upgrade_205_fixup_repl_dep(void) + return UPGRADE_SUCCESS; + } + ++/* ++ * Remove nsIndexIDListScanLimit from parentid index configuration. ++ * ++ * This attribute was incorrectly added by a previous version and can ++ * cause issues with index configuration. Remove it if present. ++ */ ++static upgrade_status ++upgrade_remove_index_scanlimit(void) ++{ ++ struct slapi_pblock *pb = slapi_pblock_new(); ++ Slapi_Entry **backends = NULL; ++ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config"; ++ const char *be_filter = "(objectclass=nsBackendInstance)"; ++ const char *attrs_to_check[] = {"parentid", NULL}; ++ upgrade_status uresult = UPGRADE_SUCCESS; ++ ++ /* Search for all backend instances */ ++ slapi_search_internal_set_pb( ++ pb, be_base_dn, ++ LDAP_SCOPE_ONELEVEL, ++ be_filter, NULL, 0, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_pb(pb); ++ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends); ++ ++ if (backends) { ++ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) { ++ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]); ++ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn"); ++ if (!be_dn || !be_name) { ++ continue; ++ } ++ ++ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) { ++ const char *attr_name = attrs_to_check[attr_idx]; ++ struct slapi_pblock *idx_pb = slapi_pblock_new(); ++ Slapi_Entry **idx_entries = NULL; ++ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s", ++ attr_name, be_dn); ++ char *idx_filter = "(objectclass=nsIndex)"; ++ ++ if (!idx_dn) { ++ slapi_pblock_destroy(idx_pb); ++ continue; ++ } ++ ++ slapi_search_internal_set_pb( ++ idx_pb, idx_dn, ++ LDAP_SCOPE_BASE, ++ idx_filter, NULL, 0, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_pb(idx_pb); ++ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries); ++ ++ if (idx_entries && idx_entries[0]) { ++ /* Check if nsIndexIDListScanLimit is present */ ++ if (slapi_entry_attr_get_ref(idx_entries[0], "nsIndexIDListScanLimit") != NULL) { ++ /* Remove nsIndexIDListScanLimit */ ++ Slapi_PBlock *mod_pb = slapi_pblock_new(); ++ Slapi_Mods smods; ++ int rc; ++ ++ slapi_mods_init(&smods, 1); ++ slapi_mods_add(&smods, LDAP_MOD_DELETE, "nsIndexIDListScanLimit", 0, NULL); ++ ++ slapi_modify_internal_set_pb( ++ mod_pb, idx_dn, ++ slapi_mods_get_ldapmods_byref(&smods), ++ NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_modify_internal_pb(mod_pb); ++ slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); ++ ++ if (rc == LDAP_SUCCESS) { ++ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_index_scanlimit", ++ "Removed 'nsIndexIDListScanLimit' from index '%s' in backend '%s'\n", ++ attr_name, be_name); ++ } else if (rc != LDAP_NO_SUCH_ATTRIBUTE) { ++ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_index_scanlimit", ++ "Failed to remove 'nsIndexIDListScanLimit' from index '%s' in backend '%s': error %d\n", ++ attr_name, be_name, rc); ++ } ++ ++ slapi_mods_done(&smods); ++ slapi_pblock_destroy(mod_pb); ++ } ++ } ++ ++ slapi_ch_free_string(&idx_dn); ++ slapi_free_search_results_internal(idx_pb); ++ slapi_pblock_destroy(idx_pb); ++ } ++ } ++ } ++ ++ slapi_free_search_results_internal(pb); ++ slapi_pblock_destroy(pb); ++ ++ return uresult; ++} ++ + /* + * Check if parentid/ancestorid indexes are missing the integerOrderingMatch + * matching rule. +@@ -407,6 +508,10 @@ upgrade_server(void) + return UPGRADE_FAILURE; + } + ++ if (upgrade_remove_index_scanlimit() != UPGRADE_SUCCESS) { ++ return UPGRADE_FAILURE; ++ } ++ + if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) { + return UPGRADE_FAILURE; + } +-- +2.52.0 + diff --git a/SOURCES/0014-Issue-6893-Log-user-that-is-updated-during-password-.patch b/SOURCES/0014-Issue-6893-Log-user-that-is-updated-during-password-.patch deleted file mode 100644 index d75bc8d..0000000 --- a/SOURCES/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/SOURCES/0014-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch b/SOURCES/0014-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch new file mode 100644 index 0000000..494a3e1 --- /dev/null +++ b/SOURCES/0014-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch @@ -0,0 +1,313 @@ +From 9a9446b9bafe25eacf97039e66d6ef19d366315c Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Thu, 5 Feb 2026 12:17:06 +0100 +Subject: [PATCH] Issue 7223 - Add upgrade function to remove ancestorid index + config entry + +Description: +Add `upgrade_remove_ancestorid_index_config()` function that removes: +* ancestorid from `cn=default indexes` +* ancestorid index config entries from each backend's `cn=index` + +Also remove ancestorid index configuration from template-dse.ldif. + +Relates: https://github.com/389ds/389-ds-base/issues/7223 + +Reviewed by: @progier389, @tbordaz, @droideck (Thanks!) +--- + .../healthcheck/health_system_indexes_test.py | 85 +++++++++++ + ldap/ldif/template-dse.ldif.in | 8 -- + ldap/servers/slapd/upgrade.c | 133 +++++++++++++++++- + 3 files changed, 214 insertions(+), 12 deletions(-) + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index b0d7a99ec..4b0c58835 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -501,6 +501,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st): + + log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index") + ++ # Verify idempotency - restart again and ensure no errors ++ log.info("Restart server again to verify idempotency (no errors on second run)") ++ standalone.restart() ++ # Verify the attribute is still absent ++ scanlimit_after_second = parentid_index.get_attr_vals_utf8("nsIndexIDListScanLimit") ++ assert not scanlimit_after_second, \ ++ f"nsIndexIDListScanLimit should still be absent after second restart but found: {scanlimit_after_second}" ++ log.info("Idempotency verified - no issues on second restart") ++ ++ ++def test_upgrade_removes_ancestorid_index_config(topology_st): ++ """Check if upgrade function removes ancestorid index config entry ++ ++ :id: 3f3d6e9b-75ac-4f0d-b2ce-7204e6eacd0a ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Use DSEldif to add an ancestorid index config entry ++ 4. Start the server (triggers upgrade) ++ 5. Verify ancestorid index config entry is removed ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. ancestorid index config entry is no longer present ++ """ ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ ANCESTORID_DN = "cn=ancestorid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Add ancestorid index config entry using DSEldif") ++ dse_ldif = DSEldif(standalone) ++ ++ # Create a fake ancestorid index entry ++ ancestorid_entry = [ ++ "dn: {}\n".format(ANCESTORID_DN), ++ "objectClass: top\n", ++ "objectClass: nsIndex\n", ++ "cn: ancestorid\n", ++ "nsSystemIndex: true\n", ++ "nsIndexType: eq\n", ++ "nsMatchingRule: integerOrderingMatch\n", ++ "\n" ++ ] ++ dse_ldif.add_entry(ancestorid_entry) ++ ++ # Verify it was added by re-reading dse.ldif ++ dse_ldif2 = DSEldif(standalone) ++ cn_value = dse_ldif2.get(ANCESTORID_DN, "cn") ++ assert cn_value is not None, "Failed to add ancestorid index config entry" ++ log.info(f"Added ancestorid index entry with cn: {cn_value}") ++ ++ log.info("Start the server (triggers upgrade)") ++ standalone.start() ++ ++ log.info("Verify ancestorid index config entry was removed by upgrade") ++ # Check via LDAP - the upgrade should have removed the entry ++ try: ++ ancestorid_index = Index(standalone, ANCESTORID_DN) ++ # If we can get the entry, it wasn't removed - this is a failure ++ cn_after = ancestorid_index.get_attr_vals_utf8("cn") ++ assert False, f"ancestorid index config entry should have been removed but still exists: {cn_after}" ++ except Exception as e: ++ # Entry should not exist - this is expected ++ log.info(f"ancestorid index config entry correctly removed (got exception: {e})") ++ ++ log.info("Upgrade successfully removed ancestorid index config entry") ++ ++ # Verify idempotency - restart again and ensure no errors ++ log.info("Restart server again to verify idempotency (no errors on second run)") ++ standalone.restart() ++ # Verify the entry is still absent ++ try: ++ ancestorid_index = Index(standalone, ANCESTORID_DN) ++ cn_after_second = ancestorid_index.get_attr_vals_utf8("cn") ++ assert False, f"ancestorid index config entry should still be absent after second restart but found: {cn_after_second}" ++ except Exception as e: ++ log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})") ++ + + if __name__ == "__main__": + # Run isolated +diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in +index 6f97d492b..70e7a85ac 100644 +--- a/ldap/ldif/template-dse.ldif.in ++++ b/ldap/ldif/template-dse.ldif.in +@@ -990,14 +990,6 @@ cn: aci + nssystemindex: true + nsindextype: pres + +-dn: cn=ancestorid,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config +-objectclass: top +-objectclass: nsIndex +-cn: ancestorid +-nssystemindex: true +-nsindextype: eq +-nsmatchingrule: integerOrderingMatch +- + dn: cn=cn,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config + objectclass: top + objectclass: nsIndex +diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c +index adfec63de..d9156cae9 100644 +--- a/ldap/servers/slapd/upgrade.c ++++ b/ldap/servers/slapd/upgrade.c +@@ -380,6 +380,126 @@ upgrade_remove_index_scanlimit(void) + return uresult; + } + ++/* ++ * Remove ancestorid index configuration entry if present. ++ * ++ * The ancestorid index is special - it has no corresponding attribute type ++ * and should not have a DSE config entry. If an entry exists, remove it. ++ * ++ * This function removes: ++ * 1. The ancestorid entry from cn=default indexes (to prevent re-creation on startup) ++ * 2. The ancestorid entry from each backend's cn=index (if it exists) ++ */ ++static upgrade_status ++upgrade_remove_ancestorid_index_config(void) ++{ ++ struct slapi_pblock *pb = slapi_pblock_new(); ++ Slapi_Entry **backends = NULL; ++ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config"; ++ const char *be_filter = "(objectclass=nsBackendInstance)"; ++ upgrade_status uresult = UPGRADE_SUCCESS; ++ int rc; ++ ++ /* ++ * First, remove ancestorid from cn=default indexes to prevent ++ * ldbm_instance_create_default_user_indexes() from re-creating it. ++ */ ++ { ++ Slapi_PBlock *def_pb = slapi_pblock_new(); ++ char *def_idx_dn = slapi_create_dn_string( ++ "cn=ancestorid,cn=default indexes,cn=config,%s", be_base_dn); ++ ++ if (def_idx_dn) { ++ slapi_delete_internal_set_pb( ++ def_pb, def_idx_dn, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_delete_internal_pb(def_pb); ++ slapi_pblock_get(def_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); ++ ++ if (rc == LDAP_SUCCESS) { ++ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config", ++ "Removed 'ancestorid' from default indexes.\n"); ++ } else if (rc != LDAP_NO_SUCH_OBJECT) { ++ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config", ++ "Failed to remove 'ancestorid' from default indexes: error %d\n", rc); ++ } ++ ++ slapi_ch_free_string(&def_idx_dn); ++ } ++ slapi_pblock_destroy(def_pb); ++ } ++ ++ /* Search for all backend instances */ ++ slapi_search_internal_set_pb( ++ pb, be_base_dn, ++ LDAP_SCOPE_ONELEVEL, ++ be_filter, NULL, 0, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_pb(pb); ++ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends); ++ ++ if (backends) { ++ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) { ++ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]); ++ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn"); ++ if (!be_dn || !be_name) { ++ continue; ++ } ++ ++ struct slapi_pblock *idx_pb = slapi_pblock_new(); ++ Slapi_Entry **idx_entries = NULL; ++ char *idx_dn = slapi_create_dn_string("cn=ancestorid,cn=index,%s", ++ be_dn); ++ char *idx_filter = "(objectclass=nsIndex)"; ++ ++ if (!idx_dn) { ++ slapi_pblock_destroy(idx_pb); ++ continue; ++ } ++ ++ slapi_search_internal_set_pb( ++ idx_pb, idx_dn, ++ LDAP_SCOPE_BASE, ++ idx_filter, NULL, 0, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_pb(idx_pb); ++ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries); ++ ++ if (idx_entries && idx_entries[0]) { ++ /* ancestorid index entry exists - delete it */ ++ Slapi_PBlock *del_pb = slapi_pblock_new(); ++ ++ slapi_delete_internal_set_pb( ++ del_pb, idx_dn, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_delete_internal_pb(del_pb); ++ slapi_pblock_get(del_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); ++ ++ if (rc == LDAP_SUCCESS) { ++ slapi_log_err(SLAPI_LOG_NOTICE, "upgrade_remove_ancestorid_index_config", ++ "Removed 'ancestorid' index config entry in backend '%s'.\n", ++ be_name); ++ } else if (rc != LDAP_NO_SUCH_OBJECT) { ++ slapi_log_err(SLAPI_LOG_ERR, "upgrade_remove_ancestorid_index_config", ++ "Failed to remove 'ancestorid' index config entry in backend '%s': error %d\n", ++ be_name, rc); ++ } ++ ++ slapi_pblock_destroy(del_pb); ++ } ++ ++ slapi_ch_free_string(&idx_dn); ++ slapi_free_search_results_internal(idx_pb); ++ slapi_pblock_destroy(idx_pb); ++ } ++ } ++ ++ slapi_free_search_results_internal(pb); ++ slapi_pblock_destroy(pb); ++ ++ return uresult; ++} ++ + /* + * Check if parentid/ancestorid indexes are missing the integerOrderingMatch + * matching rule. +@@ -394,7 +514,7 @@ upgrade_check_id_index_matching_rule(void) + Slapi_Entry **backends = NULL; + const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config"; + const char *be_filter = "(objectclass=nsBackendInstance)"; +- const char *attrs_to_check[] = {"parentid", "ancestorid", NULL}; ++ const char *attrs_to_check[] = {"parentid", NULL}; + upgrade_status uresult = UPGRADE_SUCCESS; + + /* Search for all backend instances */ +@@ -408,8 +528,9 @@ upgrade_check_id_index_matching_rule(void) + + if (backends) { + for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) { ++ const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]); + const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn"); +- if (!be_name) { ++ if (!be_dn || !be_name) { + continue; + } + +@@ -418,8 +539,8 @@ upgrade_check_id_index_matching_rule(void) + const char *attr_name = attrs_to_check[attr_idx]; + struct slapi_pblock *idx_pb = slapi_pblock_new(); + Slapi_Entry **idx_entries = NULL; +- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s", +- attr_name, be_name, be_base_dn); ++ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s", ++ attr_name, be_dn); + char *idx_filter = "(objectclass=nsIndex)"; + PRBool has_matching_rule = PR_FALSE; + +@@ -512,6 +633,10 @@ upgrade_server(void) + return UPGRADE_FAILURE; + } + ++ if (upgrade_remove_ancestorid_index_config() != UPGRADE_SUCCESS) { ++ return UPGRADE_FAILURE; ++ } ++ + if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) { + return UPGRADE_FAILURE; + } +-- +2.52.0 + diff --git a/SOURCES/0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch b/SOURCES/0015-Issue-6895-Crash-if-repl-keep-alive-entry-can-not-be.patch deleted file mode 100644 index 914d27b..0000000 --- a/SOURCES/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/SOURCES/0015-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch b/SOURCES/0015-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch new file mode 100644 index 0000000..0960428 --- /dev/null +++ b/SOURCES/0015-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch @@ -0,0 +1,300 @@ +From b5bee921b7f4cfbf7a2edbe55d4291f487f4a140 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Thu, 5 Feb 2026 12:17:06 +0100 +Subject: [PATCH] Issue 7223 - Detect and log index ordering mismatch during + backend startup + +Description: +Add `ldbm_instance_check_index_config()` function that checks on-disk +index data and logs a message in case of a mismatch with DSE config entry. + +Relates: https://github.com/389ds/389-ds-base/issues/7223 + +Reviewed by: @progier389, @tbordaz, @droideck (Thanks!) +--- + ldap/servers/slapd/back-ldbm/instance.c | 262 ++++++++++++++++++++++++ + 1 file changed, 262 insertions(+) + +diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c +index f9a546661..3fcdb5554 100644 +--- a/ldap/servers/slapd/back-ldbm/instance.c ++++ b/ldap/servers/slapd/back-ldbm/instance.c +@@ -248,6 +248,266 @@ ldbm_instance_create_default_indexes(backend *be) + } + + ++/* ++ * Check if an index has integerOrderingMatch configured in DSE. ++ * ++ * This function performs an internal LDAP search to check if the index ++ * configuration entry has nsMatchingRule: integerOrderingMatch. ++ * ++ * Parameters: ++ * inst_name - backend instance name (e.g., "userRoot") ++ * index_name - name of the index to check (e.g., "parentid", "ancestorid") ++ * ++ * Returns: ++ * PR_TRUE if integerOrderingMatch is configured ++ * PR_FALSE if not configured or index entry doesn't exist ++ */ ++static PRBool ++ldbm_instance_index_has_int_order_in_dse(const char *inst_name, const char *index_name) ++{ ++ Slapi_PBlock *pb = NULL; ++ Slapi_Entry **entries = NULL; ++ char *idx_dn = NULL; ++ PRBool has_int_order = PR_FALSE; ++ ++ idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,cn=ldbm database,cn=plugins,cn=config", ++ index_name, inst_name); ++ if (idx_dn == NULL) { ++ return PR_FALSE; ++ } ++ ++ pb = slapi_pblock_new(); ++ slapi_search_internal_set_pb(pb, idx_dn, LDAP_SCOPE_BASE, ++ "(objectclass=nsIndex)", NULL, 0, NULL, NULL, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_pb(pb); ++ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); ++ ++ if (entries && entries[0]) { ++ Slapi_Attr *mr_attr = NULL; ++ if (slapi_entry_attr_find(entries[0], "nsMatchingRule", &mr_attr) == 0) { ++ Slapi_Value *sval = NULL; ++ int idx; ++ for (idx = slapi_attr_first_value(mr_attr, &sval); ++ idx != -1; ++ idx = slapi_attr_next_value(mr_attr, idx, &sval)) { ++ const struct berval *bval = slapi_value_get_berval(sval); ++ if (bval && bval->bv_val && ++ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) { ++ has_int_order = PR_TRUE; ++ break; ++ } ++ } ++ } ++ } ++ ++ slapi_ch_free_string(&idx_dn); ++ slapi_free_search_results_internal(pb); ++ slapi_pblock_destroy(pb); ++ ++ return has_int_order; ++} ++ ++/* ++ * Check a system index for ordering mismatch between config and on-disk data. ++ * ++ * This function compares what's configured in DSE (nsMatchingRule) with ++ * what's actually on disk. A mismatch can occur in two scenarios: ++ * 1. Ordering rule is configured but disk has lexicographic order ++ * (rule was added after index was created) ++ * 2. No ordering rule configured but disk has integer order ++ * (rule was removed after index was created with it) ++ * ++ * This function reads the first keys from the specified index and checks ++ * if they are stored in lexicographic order (string: "1" < "10" < "2") or ++ * integer order (numeric: "1" < "2" < "10"). ++ * ++ * Parameters: ++ * be - backend ++ * index_name - name of the index to check (e.g., "parentid", "ancestorid") ++ * ++ */ ++static void ++ldbm_instance_check_index_config(backend *be, const char *index_name) ++{ ++ ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; ++ struct attrinfo *ai = NULL; ++ dbi_db_t *db = NULL; ++ dbi_cursor_t dbc = {0}; ++ dbi_val_t key = {0}; ++ dbi_val_t data = {0}; ++ int ret = 0; ++ PRBool config_has_int_order = PR_FALSE; ++ PRBool disk_has_int_order = PR_TRUE; /* Assume integer order until proven otherwise */ ++ ID prev_id = 0; ++ int key_count = 0; ++ PRBool first_key = PR_TRUE; ++ PRBool found_ordering_evidence = PR_FALSE; ++ ++ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config", ++ "Backend '%s': checking %s index ordering...\n", ++ inst->inst_name, index_name); ++ ++ /* Check if integerOrderingMatch is configured in DSE */ ++ config_has_int_order = ldbm_instance_index_has_int_order_in_dse(inst->inst_name, index_name); ++ ++ /* Get attrinfo for the index */ ++ ainfo_get(be, (char *)index_name, &ai); ++ if (ai == NULL || strcmp(ai->ai_type, index_name) != 0) { ++ /* No index config found */ ++ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config", ++ "Backend '%s': no %s attrinfo found, skipping check\n", ++ inst->inst_name, index_name); ++ return; ++ } ++ ++ /* Open the index file */ ++ ret = dblayer_get_index_file(be, ai, &db, 0); ++ if (ret != 0 || db == NULL) { ++ /* Index file doesn't exist or can't be opened - this is fine for new instances */ ++ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config", ++ "Backend '%s': could not open %s index file (ret=%d), skipping order check\n", ++ inst->inst_name, index_name, ret); ++ return; ++ } ++ ++ /* Create a cursor to read keys */ ++ ret = dblayer_new_cursor(be, db, NULL, &dbc); ++ if (ret != 0) { ++ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config", ++ "Backend '%s': could not create cursor on %s index (ret=%d)\n", ++ inst->inst_name, index_name, ret); ++ dblayer_release_index_file(be, ai, db); ++ return; ++ } ++ ++ dblayer_value_init(be, &key); ++ dblayer_value_init(be, &data); ++ ++ /* ++ * Read up to 100 unique keys and check their ordering. ++ * With lexicographic ordering: "1" < "10" < "100" < "2" < "20" < "3" ++ * With integer ordering: "1" < "2" < "3" < "10" < "20" < "100" ++ * ++ * If we find a case where prev_id > current_id (numerically), but the ++ * keys are still in order (lexicographically), then the index uses ++ * lexicographic ordering. ++ */ ++ while (key_count < 100) { ++ ID current_id; ++ ++ ret = dblayer_cursor_op(&dbc, first_key ? DBI_OP_MOVE_TO_FIRST : DBI_OP_NEXT_KEY, &key, &data); ++ first_key = PR_FALSE; /* Always advance cursor on next iteration */ ++ if (ret != 0) { ++ break; /* No more keys or error */ ++ } ++ ++ /* Skip non-equality keys */ ++ if (key.size < 2 || *(char *)key.data != EQ_PREFIX) { ++ continue; ++ } ++ ++ /* Parse the ID from the key (format: "=") */ ++ current_id = (ID)strtoul((char *)key.data + 1, NULL, 10); ++ if (current_id == 0) { ++ continue; /* Invalid ID, skip */ ++ } ++ ++ key_count++; ++ ++ if (prev_id != 0) { ++ /* ++ * Check ordering: if prev_id > current_id numerically, ++ * but we got this key after prev in DB order, then ++ * the index is using lexicographic ordering. ++ * ++ * Example: if we see "10" followed by "2", that's lexicographic ++ * because "10" < "2" as strings, but 10 > 2 as integers. ++ */ ++ if (prev_id > current_id) { ++ /* Found evidence of lexicographic ordering */ ++ disk_has_int_order = PR_FALSE; ++ found_ordering_evidence = PR_TRUE; ++ break; ++ } else if (prev_id < current_id) { ++ /* ++ * This is consistent with integer ordering, but we need ++ * to find a case that proves lexicographic ordering. ++ * For example, seeing "1" followed by "2" is ambiguous, ++ * but seeing "1" followed by "10" (not "2") proves lexicographic. ++ * ++ * A definitive test: if we see an ID followed by a smaller ++ * ID, that's lexicographic. If all IDs are strictly increasing, ++ * it could be either (or the index only has sequential IDs). ++ */ ++ found_ordering_evidence = PR_TRUE; ++ } ++ } ++ prev_id = current_id; ++ } ++ ++ /* Close the cursor and free values */ ++ dblayer_cursor_op(&dbc, DBI_OP_CLOSE, NULL, NULL); ++ dblayer_value_free(be, &key); ++ dblayer_value_free(be, &data); ++ ++ /* Release the index file */ ++ dblayer_release_index_file(be, ai, db); ++ ++ /* ++ * Report findings and check for config/disk mismatch. ++ * Log an error if there's a discrepancy between what's configured ++ * in DSE and what's actually on disk. ++ */ ++ if (!found_ordering_evidence) { ++ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config", ++ "Backend '%s': %s index ordering check - " ++ "could not determine on-disk ordering (index may be empty or have sequential IDs only). " ++ "Config has integerOrderingMatch: %s\n", ++ inst->inst_name, index_name, config_has_int_order ? "yes" : "no"); ++ } else if (config_has_int_order && !disk_has_int_order) { ++ /* Config expects integer ordering, but disk has lexicographic - MISMATCH */ ++ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config", ++ "Backend '%s': MISMATCH - %s index has integerOrderingMatch configured, " ++ "but on-disk data uses lexicographic ordering. " ++ "This will cause searches to return incorrect or incomplete results. " ++ "Please reindex the %s attribute: " ++ "dsconf backend index reindex --attr %s %s\n", ++ inst->inst_name, index_name, index_name, index_name, inst->inst_name); ++ } else if (!config_has_int_order && disk_has_int_order) { ++ /* Config expects lexicographic ordering, but disk has integer - MISMATCH */ ++ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config", ++ "Backend '%s': MISMATCH - %s index does not have integerOrderingMatch configured, " ++ "but on-disk data uses integer ordering. " ++ "This will cause searches to return incorrect or incomplete results. " ++ "Please reindex the %s attribute: " ++ "dsconf backend index reindex --attr %s %s\n", ++ inst->inst_name, index_name, index_name, index_name, inst->inst_name); ++ } else { ++ /* Config and disk ordering match - no action needed */ ++ slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_instance_check_index_config", ++ "Backend '%s': %s index ordering check passed - " ++ "config has integerOrderingMatch: %s, on-disk data matches.\n", ++ inst->inst_name, index_name, config_has_int_order ? "yes" : "no"); ++ } ++} ++ ++/* ++ * Check system indexes for ordering mismatches. ++ * If a mismatch is detected, log an error advising the administrator ++ * to reindex the affected attribute. ++ * ++ * Note: We only check parentid here. The ancestorid index is a special ++ * system index that has no DSE config entry - its ordering is hardcoded ++ * in ldbm_instance_init_config_entry() and cannot be changed by users. ++ */ ++static void ++ldbm_instance_check_indexes(backend *be) ++{ ++ /* Check parentid index */ ++ ldbm_instance_check_index_config(be, LDBM_PARENTID_STR); ++} ++ + /* Starts a backend instance */ + int + ldbm_instance_start(backend *be) +@@ -319,6 +579,8 @@ ldbm_instance_startall(struct ldbminfo *li) + ldbm_instance_register_modify_callback(inst); + vlv_init(inst); + slapi_mtn_be_started(inst->inst_be); ++ /* Check index configuration for potential issues */ ++ ldbm_instance_check_indexes(inst->inst_be); + } + if (slapi_exist_referral(inst->inst_be)) { + slapi_be_set_flag(inst->inst_be, SLAPI_BE_FLAG_CONTAINS_REFERRAL); +-- +2.52.0 + diff --git a/SOURCES/0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch b/SOURCES/0016-Issue-6250-Add-test-for-entryUSN-overflow-on-failed-.patch deleted file mode 100644 index 0e3949e..0000000 --- a/SOURCES/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/SOURCES/0016-Issue-7223-Add-dsctl-index-check-command-for-offline.patch b/SOURCES/0016-Issue-7223-Add-dsctl-index-check-command-for-offline.patch new file mode 100644 index 0000000..c3af0f1 --- /dev/null +++ b/SOURCES/0016-Issue-7223-Add-dsctl-index-check-command-for-offline.patch @@ -0,0 +1,1233 @@ +From 04eca1fe36480561bc2f59440d971e000133d213 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Thu, 5 Feb 2026 12:17:06 +0100 +Subject: [PATCH] Issue 7223 - Add dsctl index-check command for offline index + repair + +Description: +Add `dsctl index-check [backend] [--fix]` command for offline +detection and repair of index ordering mismatches. This is needed after +upgrade from versions that didn't use integerOrderingMatch for +parentid/ancestorid system indexes. + +It's automatically executed as part of RPM %post scriptlet during +upgrade. + +Relates: https://github.com/389ds/389-ds-base/issues/7223 + +Reviewed by: @progier389, @tbordaz, @droideck (Thanks!) +--- + .../healthcheck/health_system_indexes_test.py | 593 ++++++++++++++++++ + rpm/389-ds-base.spec.in | 51 +- + src/lib389/lib389/cli_ctl/dbtasks.py | 402 ++++++++++++ + src/lib389/lib389/dseldif.py | 51 +- + 4 files changed, 1068 insertions(+), 29 deletions(-) + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 4b0c58835..571465562 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -587,6 +587,599 @@ def test_upgrade_removes_ancestorid_index_config(topology_st): + log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})") + + ++def test_index_check_basic(topology_st): ++ """Check if dsctl index-check works correctly ++ ++ :id: 8a4e5c2d-1f3b-4a7c-9e8d-2b6f0c4a5d3e ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Run dsctl index-check while server is running (should fail) ++ 3. Stop the server ++ 4. Run dsctl index-check (should pass) ++ 5. Start the server ++ :expectedresults: ++ 1. Success ++ 2. index-check returns False and logs error ++ 3. Success ++ 4. index-check returns True (no mismatches) ++ 5. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ ++ standalone = topology_st.standalone ++ ++ log.info("Run index-check while server is running") ++ args = FakeArgs() ++ args.backend = None ++ args.fix = False ++ ++ # Server should be running, index-check should fail ++ assert standalone.status() ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False ++ assert topology_st.logcap.contains("index-check requires the instance to be stopped") ++ topology_st.logcap.flush() ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Run index-check with server stopped") ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True ++ assert topology_st.logcap.contains("All checks passed") ++ topology_st.logcap.flush() ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_specific_backend(topology_st): ++ """Check if dsctl index-check works with a specific backend ++ ++ :id: 407d8fcc-62e0-43dd-90fa-70e7090a5cfd ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Run dsctl index-check with specific backend (userRoot) ++ 4. Run dsctl index-check with non-existent backend ++ 5. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. index-check returns True for userRoot ++ 4. index-check returns False for non-existent backend ++ 5. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Run index-check for userRoot backend") ++ args = FakeArgs() ++ args.backend = "userRoot" ++ args.fix = False ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True ++ # Check for backend name in any case ++ assert topology_st.logcap.contains("Checking backend:") ++ topology_st.logcap.flush() ++ ++ log.info("Run index-check for non-existent backend") ++ args.backend = "nonExistentBackend" ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False ++ assert topology_st.logcap.contains("not found") ++ topology_st.logcap.flush() ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_mismatch_detection(topology_st): ++ """Check if dsctl index-check detects ordering mismatch ++ ++ :id: 50d14520-b0bf-4243-9fe6-b097928d4351 ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Run dsctl index-check (without --fix) ++ 4. Verify output format ++ 5. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. index-check returns True (no mismatch on fresh instance) ++ 4. Log contains expected format ++ 5. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Run index-check to verify detection logic") ++ args = FakeArgs() ++ args.backend = "userRoot" ++ args.fix = False ++ ++ # On a fresh instance, there should be no mismatch ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ # Fresh instance should have matching config and disk ordering ++ assert result is True ++ # Check that the backend was checked (may skip indexes if ordering can't be determined) ++ assert topology_st.logcap.contains("Checking backend:") ++ topology_st.logcap.flush() ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_with_fix(topology_st): ++ """Check if dsctl index-check --fix triggers reindexing ++ ++ :id: 38ae36e4-c861-4771-ae7d-354370376a2f ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Run dsctl index-check --fix (should pass since no mismatch) ++ 4. Verify output indicates check passed ++ 5. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. index-check returns True ++ 4. Log contains "All checks passed" ++ 5. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Run index-check with --fix option") ++ args = FakeArgs() ++ args.backend = None ++ args.fix = True ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ # On a fresh instance, there should be no mismatch, so no reindexing needed ++ assert result is True ++ assert topology_st.logcap.contains("All checks passed") ++ topology_st.logcap.flush() ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_fixes_scanlimit(topology_st): ++ """Check if dsctl index-check --fix removes nsIndexIDListScanLimit ++ ++ :id: 4a9b2c7d-8e1f-4b3a-9c5d-6e7f8a0b1c2d ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Add nsIndexIDListScanLimit to parentid index using DSEldif ++ 4. Run dsctl index-check (should detect issue) ++ 5. Run dsctl index-check --fix ++ 6. Verify nsIndexIDListScanLimit was removed ++ 7. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. index-check returns False and detects scanlimit ++ 5. index-check returns True after fix ++ 6. nsIndexIDListScanLimit no longer present ++ 7. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Add nsIndexIDListScanLimit to parentid index using DSEldif") ++ dse_ldif = DSEldif(standalone) ++ parentid_dn = "cn=parentid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config" ++ dse_ldif.add(parentid_dn, "nsIndexIDListScanLimit", "4000") ++ ++ # Verify it was added ++ scanlimit = dse_ldif.get(parentid_dn, "nsIndexIDListScanLimit", single=True) ++ assert scanlimit == "4000", f"Failed to add nsIndexIDListScanLimit, got: {scanlimit}" ++ log.info("Added nsIndexIDListScanLimit to parentid index") ++ ++ log.info("Run index-check without --fix (should detect issue)") ++ args = FakeArgs() ++ args.backend = "userRoot" ++ args.fix = False ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False, "index-check should detect scanlimit issue" ++ assert topology_st.logcap.contains("nsIndexIDListScanLimit") ++ topology_st.logcap.flush() ++ ++ log.info("Run index-check with --fix") ++ args.fix = True ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True, "index-check --fix should succeed" ++ assert topology_st.logcap.contains("Removed nsIndexIDListScanLimit") ++ topology_st.logcap.flush() ++ ++ log.info("Verify nsIndexIDListScanLimit was removed") ++ dse_ldif = DSEldif(standalone) # Reload to get fresh data ++ scanlimit = dse_ldif.get(parentid_dn, "nsIndexIDListScanLimit", single=True) ++ assert scanlimit is None, f"nsIndexIDListScanLimit should be removed, but got: {scanlimit}" ++ log.info("nsIndexIDListScanLimit successfully removed") ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_fixes_ancestorid_config(topology_st): ++ """Check if dsctl index-check --fix removes ancestorid config entries ++ ++ :id: 5b0c3d8e-9f2a-4c4b-0d6e-7f8a9b1c2d3e ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Add ancestorid index config entry using DSEldif ++ 4. Run dsctl index-check (should detect issue) ++ 5. Run dsctl index-check --fix ++ 6. Verify ancestorid config entry was removed ++ 7. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. index-check returns False and detects ancestorid config ++ 5. index-check returns True after fix ++ 6. ancestorid config entry no longer present ++ 7. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Add ancestorid index config entry using DSEldif") ++ dse_ldif = DSEldif(standalone) ++ ancestorid_entry = [ ++ "dn: cn=ancestorid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config\n", ++ "objectClass: top\n", ++ "objectClass: nsIndex\n", ++ "cn: ancestorid\n", ++ "nsSystemIndex: true\n", ++ "nsIndexType: eq\n", ++ ] ++ dse_ldif.add_entry(ancestorid_entry) ++ ++ # Verify it was added ++ ancestorid_dn = "cn=ancestorid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config" ++ dse_ldif = DSEldif(standalone) # Reload ++ cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True) ++ assert cn_value is not None, "Failed to add ancestorid index config entry" ++ log.info(f"Added ancestorid index entry with cn: {cn_value}") ++ ++ log.info("Run index-check without --fix (should detect issue)") ++ args = FakeArgs() ++ args.backend = "userRoot" ++ args.fix = False ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False, "index-check should detect ancestorid config issue" ++ assert topology_st.logcap.contains("ancestorid") and topology_st.logcap.contains("config entry exists") ++ topology_st.logcap.flush() ++ ++ log.info("Run index-check with --fix") ++ args.fix = True ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True, "index-check --fix should succeed" ++ assert topology_st.logcap.contains("Removed ancestorid config entry") ++ topology_st.logcap.flush() ++ ++ log.info("Verify ancestorid config entry was removed") ++ dse_ldif = DSEldif(standalone) # Reload to get fresh data ++ cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True) ++ assert cn_value is None, f"ancestorid config entry should be removed, but got: {cn_value}" ++ log.info("ancestorid config entry successfully removed") ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_fixes_missing_matching_rule(topology_st): ++ """Check if dsctl index-check --fix adds missing integerOrderingMatch ++ ++ :id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Remove integerOrderingMatch from parentid index using DSEldif ++ 4. Run dsctl index-check (should detect issue) ++ 5. Run dsctl index-check --fix ++ 6. Verify integerOrderingMatch was added back ++ 7. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. index-check returns False and detects missing matching rule ++ 5. index-check returns True after fix ++ 6. integerOrderingMatch is present ++ 7. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Remove integerOrderingMatch from parentid index using DSEldif") ++ dse_ldif = DSEldif(standalone) ++ parentid_dn = "cn=parentid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config" ++ ++ # Check current matching rules ++ matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") ++ log.info(f"Current matching rules: {matching_rules}") ++ ++ # Remove integerOrderingMatch if present ++ if matching_rules: ++ for mr in matching_rules: ++ if "integerorderingmatch" in mr.lower(): ++ dse_ldif.delete(parentid_dn, "nsMatchingRule", mr) ++ log.info(f"Removed matching rule: {mr}") ++ ++ # Verify it was removed ++ dse_ldif = DSEldif(standalone) # Reload ++ matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") ++ if matching_rules: ++ for mr in matching_rules: ++ assert "integerorderingmatch" not in mr.lower(), \ ++ f"integerOrderingMatch should be removed, but found: {mr}" ++ log.info("integerOrderingMatch removed from parentid index") ++ ++ log.info("Run index-check without --fix (should detect issue)") ++ args = FakeArgs() ++ args.backend = "userRoot" ++ args.fix = False ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False, "index-check should detect missing matching rule" ++ assert topology_st.logcap.contains("missing integerOrderingMatch") ++ topology_st.logcap.flush() ++ ++ log.info("Run index-check with --fix") ++ args.fix = True ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True, "index-check --fix should succeed" ++ assert topology_st.logcap.contains("integerOrderingMatch") ++ topology_st.logcap.flush() ++ ++ log.info("Verify integerOrderingMatch was added back") ++ dse_ldif = DSEldif(standalone) # Reload to get fresh data ++ matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") ++ assert matching_rules is not None, "nsMatchingRule should be present" ++ found_int_order = False ++ for mr in matching_rules: ++ if "integerorderingmatch" in mr.lower(): ++ found_int_order = True ++ break ++ assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}" ++ log.info("integerOrderingMatch successfully added back") ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_fixes_default_ancestorid(topology_st): ++ """Check if dsctl index-check --fix removes ancestorid from default indexes ++ ++ :id: 7d2e5f0a-1b4c-4e6d-2f8a-9b0c1d3e4f5a ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Add ancestorid to cn=default indexes using DSEldif ++ 4. Run dsctl index-check (should detect issue) ++ 5. Run dsctl index-check --fix ++ 6. Verify ancestorid was removed from default indexes ++ 7. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. index-check returns False and detects ancestorid in default indexes ++ 5. index-check returns True after fix ++ 6. ancestorid no longer in default indexes ++ 7. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ log.info("Add ancestorid to cn=default indexes using DSEldif") ++ dse_ldif = DSEldif(standalone) ++ ancestorid_default_entry = [ ++ "dn: cn=ancestorid,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config\n", ++ "objectClass: top\n", ++ "objectClass: nsIndex\n", ++ "cn: ancestorid\n", ++ "nsSystemIndex: true\n", ++ "nsIndexType: eq\n", ++ ] ++ dse_ldif.add_entry(ancestorid_default_entry) ++ ++ # Verify it was added ++ ancestorid_default_dn = "cn=ancestorid,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config" ++ dse_ldif = DSEldif(standalone) # Reload ++ cn_value = dse_ldif.get(ancestorid_default_dn, "cn", single=True) ++ assert cn_value is not None, "Failed to add ancestorid to default indexes" ++ log.info(f"Added ancestorid to default indexes with cn: {cn_value}") ++ ++ log.info("Run index-check without --fix (should detect issue)") ++ args = FakeArgs() ++ args.backend = None # Check all backends including default indexes ++ args.fix = False ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False, "index-check should detect ancestorid in default indexes" ++ assert topology_st.logcap.contains("ancestorid found in cn=default indexes") ++ topology_st.logcap.flush() ++ ++ log.info("Run index-check with --fix") ++ args.fix = True ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True, "index-check --fix should succeed" ++ assert topology_st.logcap.contains("Removed ancestorid from default indexes") ++ topology_st.logcap.flush() ++ ++ log.info("Verify ancestorid was removed from default indexes") ++ dse_ldif = DSEldif(standalone) # Reload to get fresh data ++ cn_value = dse_ldif.get(ancestorid_default_dn, "cn", single=True) ++ assert cn_value is None, f"ancestorid should be removed from default indexes, but got: {cn_value}" ++ log.info("ancestorid successfully removed from default indexes") ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ ++def test_index_check_fixes_multiple_issues(topology_st): ++ """Check if dsctl index-check --fix handles multiple issues at once ++ ++ :id: 8e3f6a1b-2c5d-4f7e-3a9b-0c1d2e4f5a6b ++ :setup: Standalone instance ++ :steps: ++ 1. Create DS instance ++ 2. Stop the server ++ 3. Add multiple issues: scanlimit, ancestorid config, missing matching rule ++ 4. Run dsctl index-check (should detect all issues) ++ 5. Run dsctl index-check --fix ++ 6. Verify all issues were fixed ++ 7. Run dsctl index-check again (should pass) ++ 8. Start the server ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. index-check returns False and detects all issues ++ 5. index-check returns True after fix ++ 6. All issues resolved ++ 7. index-check returns True (no issues) ++ 8. Success ++ """ ++ from lib389.cli_ctl.dbtasks import dbtasks_index_check ++ from lib389.dseldif import DSEldif ++ ++ standalone = topology_st.standalone ++ ++ log.info("Stop the server") ++ standalone.stop() ++ ++ dse_ldif = DSEldif(standalone) ++ parentid_dn = "cn=parentid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config" ++ ancestorid_dn = "cn=ancestorid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config" ++ ++ log.info("Add issue 1: nsIndexIDListScanLimit to parentid") ++ dse_ldif.add(parentid_dn, "nsIndexIDListScanLimit", "4000") ++ ++ log.info("Add issue 2: ancestorid index config entry") ++ ancestorid_entry = [ ++ f"dn: {ancestorid_dn}\n", ++ "objectClass: top\n", ++ "objectClass: nsIndex\n", ++ "cn: ancestorid\n", ++ "nsSystemIndex: true\n", ++ "nsIndexType: eq\n", ++ ] ++ dse_ldif.add_entry(ancestorid_entry) ++ ++ log.info("Add issue 3: Remove integerOrderingMatch from parentid") ++ dse_ldif = DSEldif(standalone) # Reload ++ matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") ++ if matching_rules: ++ for mr in matching_rules: ++ if "integerorderingmatch" in mr.lower(): ++ dse_ldif.delete(parentid_dn, "nsMatchingRule", mr) ++ ++ log.info("Run index-check without --fix (should detect all issues)") ++ args = FakeArgs() ++ args.backend = "userRoot" ++ args.fix = False ++ ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is False, "index-check should detect multiple issues" ++ # Check that multiple issues were detected ++ assert topology_st.logcap.contains("nsIndexIDListScanLimit") ++ assert topology_st.logcap.contains("ancestorid") ++ topology_st.logcap.flush() ++ ++ log.info("Run index-check with --fix") ++ args.fix = True ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True, "index-check --fix should succeed" ++ assert topology_st.logcap.contains("All issues fixed") ++ topology_st.logcap.flush() ++ ++ log.info("Verify all issues were fixed") ++ dse_ldif = DSEldif(standalone) # Reload ++ ++ # Check scanlimit removed ++ scanlimit = dse_ldif.get(parentid_dn, "nsIndexIDListScanLimit", single=True) ++ assert scanlimit is None, f"nsIndexIDListScanLimit should be removed, got: {scanlimit}" ++ ++ # Check ancestorid config removed ++ cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True) ++ assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}" ++ ++ # Check matching rule added back ++ matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") ++ found_int_order = False ++ if matching_rules: ++ for mr in matching_rules: ++ if "integerorderingmatch" in mr.lower(): ++ found_int_order = True ++ break ++ assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}" ++ ++ log.info("All issues verified as fixed") ++ ++ log.info("Run index-check again to confirm all clear") ++ args.fix = False ++ result = dbtasks_index_check(standalone, topology_st.logcap.log, args) ++ assert result is True, "index-check should pass after fix" ++ assert topology_st.logcap.contains("All checks passed") ++ topology_st.logcap.flush() ++ ++ log.info("Start the server") ++ standalone.start() ++ ++ + if __name__ == "__main__": + # Run isolated + # -s for DEBUG mode +diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in +index 44a158ce5..0175dfa7c 100644 +--- a/rpm/389-ds-base.spec.in ++++ b/rpm/389-ds-base.spec.in +@@ -645,42 +645,45 @@ if ! getent passwd $USERNAME >/dev/null ; then + fi + + # Reload our sysctl before we restart (if we can) +-sysctl --system &> $output; true ++sysctl --system &> "$output"; true + +-# Gather the running instances so we can restart them ++# Gather running instances, stop them, run index-check, then restart + instbase="%{_sysconfdir}/%{pkgname}" ++instances="" + ninst=0 +-for dir in $instbase/slapd-* ; do +- echo dir = $dir >> $output 2>&1 || : ++ ++for dir in "$instbase"/slapd-* ; do ++ echo "dir = $dir" >> "$output" 2>&1 || : + if [ ! -d "$dir" ] ; then continue ; fi + case "$dir" in *.removed) continue ;; esac +- basename=`basename $dir` +- inst="%{pkgname}@`echo $basename | sed -e 's/slapd-//g'`" +- echo found instance $inst - getting status >> $output 2>&1 || : +- if /bin/systemctl -q is-active $inst ; then +- echo instance $inst is running >> $output 2>&1 || : ++ basename=$(basename "$dir") ++ inst="%{pkgname}@${basename#slapd-}" ++ inst_name="${basename#slapd-}" ++ echo "found instance $inst - getting status" >> "$output" 2>&1 || : ++ if /bin/systemctl -q is-active "$inst" ; then ++ echo "instance $inst is running - stopping for upgrade" >> "$output" 2>&1 || : + instances="$instances $inst" ++ /bin/systemctl stop "$inst" >> "$output" 2>&1 || : + else +- echo instance $inst is not running >> $output 2>&1 || : ++ echo "instance $inst is not running" >> "$output" 2>&1 || : + fi +- ninst=`expr $ninst + 1` ++ # Run index-check on all instances (running or not) ++ # This fixes index ordering mismatches from older versions ++ dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || : ++ ninst=$((ninst + 1)) + done ++ + if [ $ninst -eq 0 ] ; then +- echo no instances to upgrade >> $output 2>&1 || : +- exit 0 # have no instances to upgrade - just skip the rest +-else +- # restart running instances +- echo shutting down all instances . . . >> $output 2>&1 || : +- for inst in $instances ; do +- echo stopping instance $inst >> $output 2>&1 || : +- /bin/systemctl stop $inst >> $output 2>&1 || : +- done +- for inst in $instances ; do +- echo starting instance $inst >> $output 2>&1 || : +- /bin/systemctl start $inst >> $output 2>&1 || : +- done ++ echo "no instances to upgrade" >> "$output" 2>&1 || : ++ exit 0 + fi + ++# Restart previously running instances ++for inst in $instances ; do ++ echo "starting instance $inst" >> "$output" 2>&1 || : ++ /bin/systemctl start "$inst" >> "$output" 2>&1 || : ++done ++ + + %preun + if [ $1 -eq 0 ]; then # Final removal +diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py +index 856639672..16da966d1 100644 +--- a/src/lib389/lib389/cli_ctl/dbtasks.py ++++ b/src/lib389/lib389/cli_ctl/dbtasks.py +@@ -7,12 +7,24 @@ + # See LICENSE for details. + # --- END COPYRIGHT BLOCK --- + ++import glob + import os ++import re ++import subprocess ++from enum import Enum + from lib389._constants import TaskWarning + from lib389.cli_base import CustomHelpFormatter ++from lib389.dseldif import DSEldif + from pathlib import Path + + ++class IndexOrdering(Enum): ++ """Represents the ordering type of an index.""" ++ INTEGER = "integer" ++ LEXICOGRAPHIC = "lexicographic" ++ UNKNOWN = "unknown" ++ ++ + def dbtasks_db2index(inst, log, args): + rtn = False + if not args.backend: +@@ -126,6 +138,387 @@ def dbtasks_verify(inst, log, args): + log.info("dbverify successful") + + ++def _get_db_dir(dse_ldif): ++ """Get the database directory. ++ ++ Args: ++ dse_ldif: DSEldif instance. ++ ++ Returns: ++ Path to the database directory, or None if not found. ++ """ ++ try: ++ db_dir = dse_ldif.get( ++ "cn=config,cn=ldbm database,cn=plugins,cn=config", ++ "nsslapd-directory", ++ single=True, ++ ) ++ return db_dir ++ except (ValueError, TypeError): ++ pass ++ return None ++ ++ ++ ++def _has_integer_ordering_match(dse_ldif, backend, index_name): ++ """Check if an index has integerOrderingMatch configured in DSE. ++ ++ Args: ++ dse_ldif: DSEldif instance. ++ backend: Backend name. ++ index_name: Name of the index to check. ++ ++ Returns: ++ True if integerOrderingMatch is configured, False otherwise. ++ """ ++ index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format( ++ index_name, backend ++ ) ++ matching_rules = dse_ldif.get(index_dn, "nsMatchingRule", lower=True) ++ if matching_rules: ++ return any(mr.lower() == "integerorderingmatch" for mr in matching_rules) ++ return False ++ ++ ++def _has_index_scan_limit(dse_ldif, backend, index_name): ++ """Check if an index has nsIndexIDListScanLimit configured. ++ ++ Args: ++ dse_ldif: DSEldif instance. ++ backend: Backend name. ++ index_name: Name of the index to check. ++ ++ Returns: ++ True if nsIndexIDListScanLimit is configured, False otherwise. ++ """ ++ index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format( ++ index_name, backend ++ ) ++ scan_limit = dse_ldif.get(index_dn, "nsIndexIDListScanLimit") ++ return scan_limit is not None ++ ++ ++def _index_config_exists(dse_ldif, backend, index_name): ++ """Check if an index configuration entry exists in DSE. ++ ++ Args: ++ dse_ldif: DSEldif instance. ++ backend: Backend name. ++ index_name: Name of the index to check. ++ ++ Returns: ++ True if the index config entry exists, False otherwise. ++ """ ++ index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format( ++ index_name, backend ++ ) ++ try: ++ cn = dse_ldif.get(index_dn, "cn") ++ return cn is not None ++ except (ValueError, KeyError): ++ return False ++ ++ ++def _default_index_exists(dse_ldif, index_name): ++ """Check if an index exists in cn=default indexes. ++ ++ Args: ++ dse_ldif: DSEldif instance. ++ index_name: Name of the index to check. ++ ++ Returns: ++ True if the index exists in default indexes, False otherwise. ++ """ ++ index_dn = "cn={},cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config".format( ++ index_name ++ ) ++ try: ++ cn = dse_ldif.get(index_dn, "cn") ++ return cn is not None ++ except (ValueError, KeyError): ++ return False ++ ++ ++def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log): ++ """Check if index on disk uses lexicographic or integer ordering. ++ ++ Args: ++ db_dir: Path to the database directory. ++ backend: Backend name. ++ index_name: Name of the index to check. ++ dbscan_path: Path to the dbscan binary. ++ is_mdb: True if using MDB backend. ++ log: Logger instance. ++ ++ Returns: ++ IndexOrdering: The detected ordering type. ++ """ ++ if is_mdb: ++ # MDB uses pseudo-paths: db_dir/backend/index.db ++ # dbscan accesses indexes via paths like: /var/lib/dirsrv/slapd-xxx/db/userroot/parentid.db ++ index_file = os.path.join(db_dir, backend, "{}.db".format(index_name)) ++ else: ++ # BDB has separate directories per backend with actual index files ++ backend_dir = os.path.join(db_dir, backend) ++ if not os.path.exists(backend_dir): ++ return IndexOrdering.UNKNOWN ++ index_file = None ++ pattern = os.path.join(backend_dir, "{}.db*".format(index_name)) ++ for f in glob.glob(pattern): ++ if os.path.isfile(f): ++ index_file = f ++ break ++ if not index_file: ++ return IndexOrdering.UNKNOWN ++ ++ try: ++ result = subprocess.run( ++ [dbscan_path, "-f", index_file], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, ++ universal_newlines=True, ++ timeout=60, ++ ) ++ ++ if result.returncode != 0: ++ log.warning(" dbscan returned non-zero exit code for %s", index_file) ++ return IndexOrdering.UNKNOWN ++ ++ # Parse keys from dbscan output ++ keys = [] ++ for line in result.stdout.split("\n"): ++ line = line.strip() ++ if line.startswith("="): ++ match = re.match(r"^=(\d+)", line) ++ if match: ++ keys.append(int(match.group(1))) ++ ++ if len(keys) < 2: ++ return IndexOrdering.UNKNOWN ++ ++ # Check if keys are in integer order by looking for decreasing numeric values ++ # (which would indicate lexicographic ordering, e.g., "3" < "30" < "4") ++ prev_id = keys[0] ++ for i in range(1, min(len(keys), 100)): ++ current_id = keys[i] ++ if prev_id > current_id: ++ return IndexOrdering.LEXICOGRAPHIC ++ prev_id = current_id ++ ++ return IndexOrdering.INTEGER ++ ++ except subprocess.TimeoutExpired: ++ log.warning(" dbscan timed out for %s", index_file) ++ return IndexOrdering.UNKNOWN ++ except OSError as e: ++ log.warning(" Error running dbscan: %s", e) ++ return IndexOrdering.UNKNOWN ++ ++ ++def dbtasks_index_check(inst, log, args): ++ """Check and optionally fix index ordering mismatches. ++ ++ This function detects mismatches between the configured ordering ++ (integerOrderingMatch in DSE) and the actual on-disk ordering of ++ parentid and ancestorid indexes. ++ ++ Args: ++ inst: DirSrv instance. ++ log: Logger instance. ++ args: Parsed command line arguments. ++ ++ Returns: ++ True if all checks passed, False if mismatches were detected. ++ """ ++ # Server must be stopped ++ if inst.status(): ++ log.error("index-check requires the instance to be stopped") ++ return False ++ ++ # Check for dbscan binary ++ dbscan_path = os.path.join(inst.ds_paths.bin_dir, "dbscan") ++ if not os.path.exists(dbscan_path): ++ log.error("dbscan utility not found at %s", dbscan_path) ++ return False ++ ++ # Load DSE ++ try: ++ dse_ldif = DSEldif(inst) ++ except Exception as e: ++ log.error("Failed to read dse.ldif: %s", e) ++ return False ++ ++ # Get backends to check ++ all_backends = dse_ldif.get_backends() ++ if not all_backends: ++ log.info("No backends found") ++ return True ++ ++ # Filter to specific backend if requested ++ if args.backend: ++ # Case-insensitive backend lookup ++ backend_lower = args.backend.lower() ++ matching_backend = None ++ for be in all_backends: ++ if be.lower() == backend_lower: ++ matching_backend = be ++ break ++ if matching_backend is None: ++ log.error("Backend '%s' not found. Available backends: %s", ++ args.backend, ", ".join(all_backends)) ++ return False ++ backends_to_check = [matching_backend] ++ else: ++ backends_to_check = all_backends ++ ++ # Get database directory and check database type ++ db_dir = _get_db_dir(dse_ldif) ++ if not db_dir or not os.path.exists(db_dir): ++ log.error("Database directory not found") ++ return False ++ ++ db_lib = inst.get_db_lib() ++ is_mdb = (db_lib == "mdb") ++ log.info("Database type: %s", db_lib.upper()) ++ ++ # Track all issues found ++ all_ok = True ++ mismatches = [] # (backend, index_name) tuples needing reindex ++ missing_matching_rules = [] # (backend, index_name) tuples missing integerOrderingMatch ++ scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit ++ ancestorid_configs_to_remove = [] # backend names with ancestorid config entries ++ remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes ++ ++ # Check if ancestorid exists in cn=default indexes (should be removed) ++ if _default_index_exists(dse_ldif, "ancestorid"): ++ log.warning("ancestorid found in cn=default indexes - should be removed") ++ remove_ancestorid_from_defaults = True ++ all_ok = False ++ ++ for backend in backends_to_check: ++ log.info("Checking backend: %s", backend) ++ ++ # Check for ancestorid config entry (should not exist) ++ if _index_config_exists(dse_ldif, backend, "ancestorid"): ++ log.warning(" ancestorid - config entry exists (should be removed)") ++ ancestorid_configs_to_remove.append(backend) ++ all_ok = False ++ ++ # Check parentid and ancestorid indexes ++ for index_name in ["parentid", "ancestorid"]: ++ # Check for scan limits (should be removed) ++ if _has_index_scan_limit(dse_ldif, backend, index_name): ++ log.warning(" %s - has nsIndexIDListScanLimit (should be removed)", index_name) ++ scan_limits_to_remove.append((backend, index_name)) ++ all_ok = False ++ ++ # Check disk ordering ++ disk_ordering = _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log) ++ ++ if disk_ordering == IndexOrdering.UNKNOWN: ++ log.info(" %s - could not determine disk ordering, skipping", index_name) ++ # For parentid, still check if matching rule is missing ++ if index_name == "parentid": ++ config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name) ++ if not config_has_int_order: ++ log.warning(" %s - missing integerOrderingMatch in config", index_name) ++ missing_matching_rules.append((backend, index_name)) ++ all_ok = False ++ continue ++ ++ config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name) ++ config_desc = "integer" if config_has_int_order else "lexicographic" ++ log.info(" %s - config: %s, disk: %s", ++ index_name, config_desc, disk_ordering.value) ++ ++ # For parentid, the desired state is always integer ordering ++ if index_name == "parentid": ++ if not config_has_int_order: ++ log.warning(" %s - missing integerOrderingMatch in config", index_name) ++ if (backend, index_name) not in missing_matching_rules: ++ missing_matching_rules.append((backend, index_name)) ++ all_ok = False ++ ++ if disk_ordering == IndexOrdering.LEXICOGRAPHIC: ++ log.warning(" %s - disk ordering is lexicographic, needs reindex", index_name) ++ if (backend, index_name) not in mismatches: ++ mismatches.append((backend, index_name)) ++ all_ok = False ++ ++ # Handle issues ++ if not all_ok: ++ if args.fix: ++ log.info("Fixing issues...") ++ ++ # Remove ancestorid from cn=default indexes ++ if remove_ancestorid_from_defaults: ++ default_idx_dn = "cn=ancestorid,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config" ++ log.info(" Removing ancestorid from default indexes...") ++ try: ++ dse_ldif.delete_dn(default_idx_dn) ++ log.info(" Removed ancestorid from default indexes") ++ except Exception as e: ++ log.error(" Failed to remove ancestorid from default indexes: %s", e) ++ return False ++ ++ # Remove scan limits (only for indexes that won't be deleted) ++ for backend, index_name in scan_limits_to_remove: ++ # Skip ancestorid if we're going to delete the whole entry anyway ++ if index_name == "ancestorid" and backend in ancestorid_configs_to_remove: ++ continue ++ index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format( ++ index_name, backend ++ ) ++ log.info(" Removing nsIndexIDListScanLimit from %s in backend %s...", index_name, backend) ++ try: ++ dse_ldif.delete(index_dn, "nsIndexIDListScanLimit") ++ log.info(" Removed nsIndexIDListScanLimit from %s", index_name) ++ except Exception as e: ++ log.error(" Failed to remove nsIndexIDListScanLimit from %s: %s", index_name, e) ++ return False ++ ++ # Remove ancestorid config entries from backends ++ for backend in ancestorid_configs_to_remove: ++ index_dn = "cn=ancestorid,cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format(backend) ++ log.info(" Removing ancestorid config entry from backend %s...", backend) ++ try: ++ dse_ldif.delete_dn(index_dn) ++ log.info(" Removed ancestorid config entry from backend %s", backend) ++ except Exception as e: ++ log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e) ++ return False ++ ++ # Add missing matching rules to dse.ldif ++ for backend, index_name in missing_matching_rules: ++ index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format( ++ index_name, backend ++ ) ++ log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend) ++ try: ++ dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch") ++ log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name) ++ except Exception as e: ++ log.error(" Failed to update dse.ldif for %s: %s", index_name, e) ++ return False ++ ++ # Reindex indexes with disk ordering issues ++ for backend, index_name in mismatches: ++ log.info(" Reindexing %s in backend %s...", index_name, backend) ++ if not inst.db2index(bename=backend, attrs=[index_name]): ++ log.error(" Failed to reindex %s", index_name) ++ return False ++ log.info(" Reindex of %s completed successfully", index_name) ++ ++ log.info("All issues fixed") ++ return True ++ else: ++ log.info("Issues detected. Run with --fix to repair.") ++ return False ++ else: ++ log.info("All checks passed - no issues found") ++ return True ++ ++ + def create_parser(subcommands): + db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter) + # db2index_parser.add_argument('suffix', help="The suffix to reindex. IE dc=example,dc=com.") +@@ -172,3 +565,12 @@ def create_parser(subcommands): + ldifs_parser = subcommands.add_parser('ldifs', help="List all the LDIF files located in the server's LDIF directory", formatter_class=CustomHelpFormatter) + ldifs_parser.add_argument('--delete', nargs=1, help="Delete LDIF file") + ldifs_parser.set_defaults(func=dbtasks_ldifs) ++ ++ index_check_parser = subcommands.add_parser('index-check', ++ help="Check for index ordering mismatches (parentid/ancestorid). The server must be stopped.", ++ formatter_class=CustomHelpFormatter) ++ index_check_parser.add_argument('backend', nargs='?', default=None, ++ help="Backend to check. If not specified, all backends are checked.") ++ index_check_parser.add_argument('--fix', action='store_true', default=False, ++ help="Fix mismatches by reindexing affected indexes") ++ index_check_parser.set_defaults(func=dbtasks_index_check) +diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py +index d12c6424c..7834d9468 100644 +--- a/src/lib389/lib389/dseldif.py ++++ b/src/lib389/lib389/dseldif.py +@@ -125,11 +125,14 @@ class DSEldif(DSLint): + self._contents[i] = self._contents[i].replace(strfrom, strto) + self._update() + +- def _find_attr(self, entry_dn, attr): ++ def _find_attr(self, entry_dn, attr, lower=False): + """Find all attribute values and indexes under a given entry + + Returns entry dn index and attribute data dict: + relative attribute indexes and the attribute value ++ ++ :param lower: Use case-insensitive matching for attribute name ++ :type lower: boolean + """ + + entry_dn_i = self._contents.index("dn: {}\n".format(entry_dn.lower())) +@@ -146,7 +149,11 @@ class DSEldif(DSLint): + + # Find the attribute + for line in entry_slice: +- if line.startswith("{}:".format(attr)): ++ if lower: ++ match = line.lower().startswith("{}:".format(attr.lower())) ++ else: ++ match = line.startswith("{}:".format(attr)) ++ if match: + attr_value = line.split(" ", 1)[1][:-1] + attr_data.update({entry_slice.index(line): attr_value}) + +@@ -155,7 +162,7 @@ class DSEldif(DSLint): + + return entry_dn_i, attr_data + +- def get(self, entry_dn, attr, single=False): ++ def get(self, entry_dn, attr, single=False, lower=False): + """Return attribute values under a given entry + + :param entry_dn: a DN of entry we want to get attribute from +@@ -163,11 +170,13 @@ class DSEldif(DSLint): + :param attr: an attribute name + :type attr: str + :param single: Return a single value instead of a list +- :type sigle: boolean ++ :type single: boolean ++ :param lower: Use case-insensitive matching for attribute name ++ :type lower: boolean + """ + + try: +- _, attr_data = self._find_attr(entry_dn, attr) ++ _, attr_data = self._find_attr(entry_dn, attr, lower=lower) + except ValueError: + return None + +@@ -190,6 +199,38 @@ class DSEldif(DSLint): + + return indexes + ++ def get_backends(self): ++ """Return a list of backend names from DSE. ++ ++ Returns backend names preserving their original case, as the ++ database directory names on disk use the original case. ++ ++ Note: DSEldif lowercases DN lines, so we read the 'cn' attribute ++ from each entry to get the original case. ++ ++ :returns: List of backend names ++ """ ++ backends = [] ++ excluded = ("config", "monitor", "index", "encrypted attributes") ++ ++ for entry in self._contents: ++ if (entry.startswith("dn: cn=") and ++ ",cn=ldbm database,cn=plugins,cn=config" in entry): ++ parts = entry.split(",") ++ if len(parts) > 1: ++ cn_lower = parts[0].replace("dn: cn=", "") ++ if cn_lower not in excluded: ++ dn = entry.strip()[4:].strip() ++ try: ++ suffix = self.get(dn, "nsslapd-suffix") ++ if suffix: ++ cn_values = self.get(dn, "cn") ++ if cn_values: ++ backends.append(cn_values[0]) ++ except (ValueError, IndexError): ++ pass ++ ++ return list(set(backends)) + + def add_entry(self, entry): + """Add a new entry +-- +2.52.0 + diff --git a/SOURCES/0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch b/SOURCES/0017-Issue-6594-Add-test-for-numSubordinates-replication-.patch deleted file mode 100644 index 9680aa3..0000000 --- a/SOURCES/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/SOURCES/0017-Issue-7096-2nd-During-replication-online-total-init-.patch b/SOURCES/0017-Issue-7096-2nd-During-replication-online-total-init-.patch new file mode 100644 index 0000000..724a0b7 --- /dev/null +++ b/SOURCES/0017-Issue-7096-2nd-During-replication-online-total-init-.patch @@ -0,0 +1,135 @@ +From c1f0756b453b7ea219add236f60a72b3fc660af0 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Tue, 27 Jan 2026 14:26:29 +0100 +Subject: [PATCH] Issue 7096 - (2nd) During replication online total init the + function idl_id_is_in_idlist is not scaling with large database (#7205) + +Bug Description: +The fix for #7096 optimized the BDB backend's `idl_new_range_fetch()` +function to use ID ranges instead of checking the full ID list during +online total initialization. However, the LMDB backend's +`idl_lmdb_range_fetch()` function and its callback +`idl_range_add_id_cb()` were not updated and still use the non-scaling +`idl_id_is_in_idlist()` function. + +Fix Description: +Apply the same optimization to the LMDB backend. + +Fixes: https://github.com/389ds/389-ds-base/issues/7096 + +Reviewed by: @tbordaz, @droideck (Thanks!) +--- + ldap/servers/slapd/back-ldbm/idl_new.c | 39 ++++++++++---------------- + 1 file changed, 15 insertions(+), 24 deletions(-) + +diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c +index 2d978353f..613d53815 100644 +--- a/ldap/servers/slapd/back-ldbm/idl_new.c ++++ b/ldap/servers/slapd/back-ldbm/idl_new.c +@@ -66,6 +66,7 @@ typedef struct { + size_t leftoverlen; + size_t leftovercnt; + IDList *idl; ++ IdRange_t *idrange_list; + int flag_err; + ID lastid; + ID suffix; +@@ -700,9 +701,9 @@ error: + } + } + } +- slapi_ch_free((void **)&leftover); +- idrange_free(&idrange_list); + } ++ slapi_ch_free((void **)&leftover); ++ idrange_free(&idrange_list); + slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch", + "Found %d candidates; error code is: %d\n", + idl ? idl->b_nids : 0, *flag_err); +@@ -716,7 +717,6 @@ static int + idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx) + { + idl_range_ctx_t *rctx = ctx; +- int idl_rc = 0; + ID id = 0; + + if (key->data == NULL) { +@@ -779,10 +779,12 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx) + * found entry is the one from the suffix + */ + rctx->suffix = keyval; +- idl_rc = idl_append_extend(&rctx->idl, id); +- } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist(rctx->idl, keyval)) { ++ idl_append_extend(&rctx->idl, id); ++ idrange_add_id(&rctx->idrange_list, id); ++ } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist_ranges(rctx->idl, rctx->idrange_list, keyval)) { + /* the parent is the suffix or already in idl. */ +- idl_rc = idl_append_extend(&rctx->idl, id); ++ idl_append_extend(&rctx->idl, id); ++ idrange_add_id(&rctx->idrange_list, id); + } else { + /* Otherwise, keep the {keyval,id} in leftover array */ + if (!rctx->leftover) { +@@ -797,14 +799,7 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx) + rctx->leftovercnt++; + } + } else { +- idl_rc = idl_append_extend(&rctx->idl, id); +- } +- if (idl_rc) { +- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch", +- "Unable to extend id list (err=%d)\n", idl_rc); +- idl_free(&rctx->idl); +- rctx->flag_err = LDAP_UNWILLING_TO_PERFORM; +- return DBI_RC_NOTFOUND; ++ idl_append_extend(&rctx->idl, id); + } + #if defined(DB_ALLIDS_ON_READ) + /* enforce the allids read limit */ +@@ -841,7 +836,6 @@ idl_lmdb_range_fetch( + { + int ret = 0; + int ret2 = 0; +- int idl_rc = 0; + dbi_cursor_t cursor = {0}; + back_txn s_txn; + struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private; +@@ -891,6 +885,7 @@ idl_lmdb_range_fetch( + idl_range_ctx.lastid = 0; + idl_range_ctx.count = 0; + idl_range_ctx.index_id = index_id; ++ idl_range_ctx.idrange_list = NULL; + if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) { + struct _back_info_index_key bck_info; + /* We are doing a bulk import +@@ -966,22 +961,18 @@ error: + while(remaining > 0) { + for (size_t i = 0; i < idl_range_ctx.leftovercnt; i++) { + if (idl_range_ctx.leftover[i].key > 0 && +- idl_id_is_in_idlist(idl_range_ctx.idl, idl_range_ctx.leftover[i].key) != 0) { ++ idl_id_is_in_idlist_ranges(idl_range_ctx.idl, idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].key) != 0) { + /* if the leftover key has its parent in the idl */ +- idl_rc = idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id); +- if (idl_rc) { +- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch", +- "Unable to extend id list (err=%d)\n", idl_rc); +- idl_free(&idl_range_ctx.idl); +- break; +- } ++ idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id); ++ idrange_add_id(&idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].id); + idl_range_ctx.leftover[i].key = 0; + remaining--; + } + } + } +- slapi_ch_free((void **)&idl_range_ctx.leftover); + } ++ slapi_ch_free((void **)&idl_range_ctx.leftover); ++ idrange_free(&idl_range_ctx.idrange_list); + *flag_err = idl_range_ctx.flag_err; + slapi_log_err(SLAPI_LOG_FILTER, "idl_lmdb_range_fetch", + "Found %d candidates; error code is: %d\n", +-- +2.52.0 + diff --git a/SOURCES/0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch b/SOURCES/0018-Issue-6884-Mask-password-hashes-in-audit-logs-6885.patch deleted file mode 100644 index f7f9a2b..0000000 --- a/SOURCES/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/SOURCES/0018-Issue-7076-6992-6784-6214-Fix-CI-test-failures-7077.patch b/SOURCES/0018-Issue-7076-6992-6784-6214-Fix-CI-test-failures-7077.patch new file mode 100644 index 0000000..dfd9482 --- /dev/null +++ b/SOURCES/0018-Issue-7076-6992-6784-6214-Fix-CI-test-failures-7077.patch @@ -0,0 +1,877 @@ +From 35237d57daf6fdf20a42c3cef27130ba70f0cdc2 Mon Sep 17 00:00:00 2001 +From: Akshay Adhikari +Date: Tue, 18 Nov 2025 21:57:10 +0530 +Subject: [PATCH] Issue 7076, 6992, 6784, 6214 - Fix CI test failures (#7077) + +- Fixed import test bugs in regression_test.py (cleanup handler, LDIF permissions) - + https://github.com/389ds/389-ds-base/issues/6992 +- Fixed ModRDN cache corruption on failed operations (parent update check, cache cleanup) +- Fixed attribute uniqueness test fixture cleanup in attruniq_test.py +- mproved test stability by fixing race conditions in replication, healthcheck, + web UI, memberOf, and basic tests. +- Fixed entrycache_eviction_test.py to track incremental log counts instead of cumulative - + https://github.com/389ds/389-ds-base/issues/6784 + +Fixes: https://github.com/389ds/389-ds-base/issues/7076 +Relates: https://github.com/389ds/389-ds-base/issues/6992 +Relates: https://github.com/389ds/389-ds-base/issues/6784 +Fixes: https://github.com/389ds/389-ds-base/issues/6214 + +Reviewed by: @vashirov, @progier389 (Thanks!) +--- + dirsrvtests/tests/suites/basic/basic_test.py | 4 +- + .../healthcheck/health_system_indexes_test.py | 3 + + .../tests/suites/import/regression_test.py | 388 +++++++++++++++++- + .../suites/memberof_plugin/fixup_test.py | 8 +- + .../tests/suites/plugins/attruniq_test.py | 84 ++-- + .../suites/replication/regression_m2_test.py | 4 + + .../replication/repl_log_monitoring_test.py | 8 +- + .../suites/webui/database/database_test.py | 1 + + ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 44 +- + 9 files changed, 481 insertions(+), 63 deletions(-) + +diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py +index be825efe9..e9b611439 100644 +--- a/dirsrvtests/tests/suites/basic/basic_test.py ++++ b/dirsrvtests/tests/suites/basic/basic_test.py +@@ -593,7 +593,7 @@ def test_basic_import_export(topology_st, import_example_ldif): + # + # Test online/offline LDIF imports + # +- topology_st.standalone.start() ++ topology_st.standalone.restart() + # topology_st.standalone.config.set('nsslapd-errorlog-level', '1') + + # Generate a test ldif (50k entries) +@@ -691,6 +691,8 @@ def test_basic_backup(topology_st, import_example_ldif): + + log.info('Running test_basic_backup...') + ++ topology_st.standalone.restart() ++ + backup_dir = topology_st.standalone.get_bak_dir() + '/backup_test_online' + log.info(f'Backup directory is {backup_dir}') + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 571465562..4cd4d0c70 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -172,6 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled): + log.info("Re-add the parentId index") + backend = Backends(standalone).get("userRoot") + backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"]) ++ standalone.restart() + + run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +@@ -215,6 +216,7 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled): + log.info("Re-add the integerOrderingMatch matching rule") + parentid_index = Index(standalone, PARENTID_DN) + parentid_index.add("nsMatchingRule", "integerOrderingMatch") ++ standalone.restart() + + run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +@@ -445,6 +447,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled): + backend = Backends(standalone).get("userRoot") + backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"]) + backend.add_index("nsuniqueid", ["eq"]) ++ standalone.restart() + + run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) + run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py +index 18611de35..bdbb516e8 100644 +--- a/dirsrvtests/tests/suites/import/regression_test.py ++++ b/dirsrvtests/tests/suites/import/regression_test.py +@@ -5,6 +5,7 @@ + # See LICENSE for details. + # --- END COPYRIGHT BLOCK --- + # ++from abc import ABC, abstractmethod + from decimal import * + import ldap + import logging +@@ -16,9 +17,9 @@ from lib389.backend import Backends + from lib389.properties import TASK_WAIT + from lib389.topologies import topology_st as topo + from lib389.dbgen import dbgen_users +-from lib389._constants import DEFAULT_SUFFIX ++from lib389._constants import DEFAULT_SUFFIX, DEFAULT_BENAME + from lib389.tasks import * +-from lib389.idm.user import UserAccounts ++from lib389.idm.user import UserAccounts, UserAccount + from lib389.idm.directorymanager import DirectoryManager + from lib389.dbgen import * + from lib389.utils import * +@@ -92,7 +93,203 @@ class AddDelUsers(threading.Thread): + return self._ran + + +-def test_replay_import_operation(topo): ++def get_backend_by_name(inst, bename): ++ bes = Backends(inst) ++ bename = bename.lower() ++ be = [ be for be in bes if be.get_attr_val_utf8_l('cn') == bename ] ++ return be[0] if len(be) == 1 else None ++ ++ ++class LogHandler(): ++ def __init__(self, logfd, patterns): ++ self.logfd = logfd ++ self.patterns = [ p.lower() for p in patterns ] ++ self.pos = logfd.tell() ++ self.last_result = None ++ ++ def zero(self): ++ return [0 for _ in range(len(self.patterns))] ++ ++ def countCaptures(self): ++ res = self.zero() ++ self.logfd.seek(self.pos) ++ for line in iter(self.logfd.readline, ''): ++ # Ignore autotune messages that may confuse the counts ++ if 'bdb_start_autotune' in line: ++ continue ++ # Ignore LMDB size warnings that may confuse the counts ++ if 'dbmdb_ctx_t_db_max_size_set' in line: ++ continue ++ log.info(f'ERROR LOG line is {line.strip()}') ++ for idx,pattern in enumerate(self.patterns): ++ if pattern in line.lower(): ++ res[idx] += 1 ++ self.pos = self.logfd.tell() ++ self.last_result = res ++ log.info(f'ERROR LOG counts are: {res}') ++ return res ++ ++ def seek2end(self): ++ self.pos = os.fstat(self.logfd.fileno()).st_size ++ ++ def check(self, idx, val): ++ count = self.last_result[idx] ++ assert count == val , f"Should have {val} '{self.patterns[idx]}' messages but got: {count} - idx = {idx}" ++ ++ ++class IEHandler(ABC): ++ def __init__(self, inst, errlog, ldifname, bename=DEFAULT_BENAME, suffix=None): ++ self.inst = inst ++ self.errlog = errlog ++ self.ldifname = ldifname ++ self.bename = bename ++ self.suffix = suffix ++ self.ldif = ldifname if ldifname.startswith('/') else f'{inst.get_ldif_dir()}/{ldifname}.ldif' ++ ++ @abstractmethod ++ def get_name(self): ++ pass ++ ++ @abstractmethod ++ def _run_task_b(self): ++ pass ++ ++ @abstractmethod ++ def _run_task_s(self): ++ pass ++ ++ @abstractmethod ++ def _run_offline(self): ++ pass ++ ++ @abstractmethod ++ def _set_log_pattern(self, success): ++ pass ++ ++ def run(self, extra_checks, success=True): ++ if self.errlog: ++ self._set_log_pattern(success) ++ self.errlog.seek2end() ++ ++ if self.inst.status(): ++ if self.bename: ++ log.info(f"Performing online {self.get_name()} of backend {self.bename} into LDIF file {self.ldif}") ++ r = self._run_task_b() ++ else: ++ log.info(f"Performing online {self.get_name()} of suffix {self.suffix} into LDIF file {self.ldif}") ++ r = self._run_task_s() ++ r.wait() ++ time.sleep(1) ++ else: ++ if self.bename: ++ log.info(f"Performing offline {self.get_name()} of backend {self.bename} into LDIF file {self.ldif}") ++ else: ++ log.info(f"Performing offline {self.get_name()} of suffix {self.suffix} into LDIF file {self.ldif}") ++ self._run_offline() ++ if self.errlog: ++ expected_counts = ['*' for _ in range(len(self.errlog.patterns))] ++ for (idx, val) in extra_checks: ++ expected_counts[idx] = val ++ res = self.errlog.countCaptures() ++ log.info(f'Expected errorlog counts are: {expected_counts}') ++ if success is True or success is False: ++ log.info(f'Number of {self.errlog.patterns[0]} in errorlog is: {res[0]}') ++ assert res[0] >= 1 ++ for (idx, val) in extra_checks: ++ self.errlog.check(idx, val) ++ ++ def check_db(self): ++ assert self.inst.dbscan(bename=self.bename, index='id2entry') ++ ++ ++class Importer(IEHandler): ++ def get_name(self): ++ return "import" ++ ++ def _set_log_pattern(self, success): ++ if success is True: ++ self.errlog.patterns[0] = 'import complete' ++ elif success is False: ++ self.errlog.patterns[0] = 'import failed' ++ ++ def _run_task_b(self): ++ bes = Backends(self.inst) ++ r = bes.import_ldif(self.bename, [self.ldif,], include_suffixes=self.suffix) ++ return r ++ ++ def _run_task_s(self): ++ r = ImportTask(self.inst) ++ r.import_suffix_from_ldif(self.ldif, self.suffix) ++ return r ++ ++ def _run_offline(self): ++ log.info(f'self.inst.ldif2db({self.bename}, {self.suffix}, ...)') ++ if self.suffix is None: ++ self.inst.ldif2db(self.bename, self.suffix, None, False, self.ldif) ++ else: ++ self.inst.ldif2db(self.bename, [self.suffix, ], None, False, self.ldif) ++ ++ ++class Exporter(IEHandler): ++ def get_name(self): ++ return "export" ++ ++ def _set_log_pattern(self, success): ++ if success is True: ++ self.errlog.patterns[0] = 'export finished' ++ elif success is False: ++ self.errlog.patterns[0] = 'export failed' ++ ++ def _run_task_b(self): ++ bes = Backends(self.inst) ++ r = bes.export_ldif(self.bename, self.ldif, include_suffixes=self.suffix) ++ return r ++ ++ def _run_task_s(self): ++ r = ExportTask(self.inst) ++ r.export_suffix_to_ldif(self.ldif, self.suffix) ++ return r ++ ++ def _run_offline(self): ++ self.inst.db2ldif(self.bename, self.suffix, None, False, False, self.ldif) ++ ++ ++def preserve_func(topo, request, restart): ++ # Ensure that topology get preserved helper ++ inst = topo.standalone ++ ++ def fin(): ++ if restart: ++ inst.restart() ++ Importer(inst, None, "save").run(()) ++ ++ r = Exporter(inst, None, "save") ++ if not os.path.isfile(r.ldif): ++ r.run(()) ++ request.addfinalizer(fin) ++ ++ ++@pytest.fixture(scope="function") ++def preserve(topo, request): ++ # Ensure that topology get preserved (no restart) ++ preserve_func(topo, request, False) ++ ++ ++@pytest.fixture(scope="function") ++def preserve_r(topo, request): ++ # Ensure that topology get preserved (with restart) ++ preserve_func(topo, request, True) ++ ++ ++@pytest.fixture(scope="function") ++def verify(topo): ++ # Check that backend is not broken ++ inst = topo.standalone ++ dn=f'uid=demo_user,ou=people,{DEFAULT_SUFFIX}' ++ assert UserAccount(inst,dn).exists() ++ ++ ++def test_replay_import_operation(topo, preserve_r, verify): + """ Check after certain failed import operation, is it + possible to replay an import operation + +@@ -487,7 +684,190 @@ def test_ldif2db_after_backend_create(topo): + import_time_2 = create_backend_and_import(instance, ldif_file_2, 'o=test_2', 'test_2') + + log.info('Import times should be approximately the same') +- assert abs(import_time_1 - import_time_2) < 5 ++ assert abs(import_time_1 - import_time_2) < 15 ++ ++ ++def test_ldif_missing_suffix_entry(topo, request, verify): ++ """Test that ldif2db/import aborts if suffix entry is not in the ldif ++ ++ :id: 731bd0d6-8cc8-11f0-8ef2-c85309d5c3e3 ++ :setup: Standalone Instance ++ :steps: ++ 1. Prepare final cleanup ++ 2. Add a few users ++ 3. Export ou=people subtree ++ 4. Online import using backend name ou=people subtree ++ 5. Online import using suffix name ou=people subtree ++ 6. Stop the instance ++ 7. Offline import using backend name ou=people subtree ++ 8. Offline import using suffix name ou=people subtree ++ 9. Generate ldif with a far away suffix ++ 10. Offline import using backend name and "far" ldif ++ 11. Offline import using suffix name and "far" ldif ++ 12. Start the instance ++ 13. Online import using backend name and "far" ldif ++ 14. Online import using suffix name and "far" ldif ++ :expectedresults: ++ 1. Operation successful ++ 2. Operation successful ++ 3. Operation successful ++ 4. Import should success, skip all entries, db should exists ++ 5. Import should success, skip all entries, db should exists ++ 6. Operation successful ++ 7. Import should success, skip all entries, db should exists ++ 8. Import should success, skip all entries, db should exists ++ 9. Operation successful ++ 10. Import should success, skip all entries, db should exists ++ 11. Import should success, 10 entries skipped, db should exists ++ 12. Operation successful ++ 13. Import should success, skip all entries, db should exists ++ 14. Import should success, 10 entries skipped, db should exists ++ """ ++ ++ inst = topo.standalone ++ inst.config.set('nsslapd-errorlog-level', '266354688') ++ no_suffix_on = ( ++ (1, 0), # no errors are expected. ++ (2, 1), # 1 warning is expected. ++ (3, 0), # no 'no parent' warning is expected. ++ (4, 1), # 1 'all entries were skipped' warning ++ (5, 0), # no 'returning task warning' info message ++ ) ++ no_suffix_off = ( ++ (1, 0), # no errors are expected. ++ (2, 1), # 1 warning is expected. ++ (3, 0), # no 'no parent' warning is expected. ++ (4, 1), # 1 'all entries were skipped' warning ++ (5, 1), # 1 'returning task warning' info message ++ ) ++ ++ far_suffix_on = ( ++ (1, 0), # no errors are expected. ++ (2, 1), # 1 warning (consolidated, pre-check aborts after 4 entries) ++ (3, 0), # 0 'no parent' warnings (pre-check aborts before processing) ++ (4, 1), # 1 'all entries were skipped' warning (from pre-check) ++ (5, 0), # 0 'returning task warning' info message (online import) ++ ) ++ # Backend-specific behavior for orphan detection when suffix parameter is provided ++ nbw = 0 if get_default_db_lib() == "bdb" else 10 ++ far_suffix_with_suffix_on = ( ++ (1, 0), # no errors are expected. ++ (2, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) warnings ++ (3, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) 'no parent' warnings ++ (4, 0), # 0 'all entries were skipped' warning (no pre-check abort) ++ (5, 0), # 0 'returning task warning' info message (online import) ++ ) ++ far_suffix_off = ( ++ (1, 0), # no errors are expected. ++ (2, 1), # 1 warning (consolidated, pre-check detects missing suffix) ++ (3, 0), # 0 'no parent' warnings (pre-check aborts before processing) ++ (4, 1), # 1 'all entries were skipped' warning (from pre-check) ++ (5, 1), # 1 'returning task warning' info message (offline import) ++ ) ++ far_suffix_with_suffix_off = ( ++ (1, 0), # no errors are expected. ++ (2, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) warnings ++ (3, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) 'no parent' warnings ++ (4, 0), # 0 'all entries were skipped' warning (no pre-check abort) ++ (5, 0), # 0 'returning task warning' (rc=0, successful import of suffix) ++ ) ++ ++ with open(inst.ds_paths.error_log, 'at+') as fd: ++ patterns = ( ++ "Reserved for IEHandler", ++ " ERR ", ++ " WARN ", ++ "has no parent", ++ "all entries were skipped", ++ "returning task warning", ++ ) ++ errlog = LogHandler(fd, patterns) ++ no_errors = ((1, 0), (2, 0)) # no errors nor warnings are expected. ++ ++ ++ # 1. Prepare final cleanup ++ Exporter(inst, errlog, "full").run(no_errors) ++ ++ def fin(): ++ inst.start() ++ with open(inst.ds_paths.error_log, 'at+') as cleanup_fd: ++ cleanup_errlog = LogHandler(cleanup_fd, patterns) ++ Importer(inst, cleanup_errlog, "full").run(no_errors) ++ ++ if not DEBUGGING: ++ request.addfinalizer(fin) ++ ++ # 2. Add a few users ++ user = UserAccounts(inst, DEFAULT_SUFFIX) ++ users = [ user.create_test_user(uid=i) for i in range(10) ] ++ ++ # 3. Export ou=people subtree ++ e = Exporter(inst, errlog, "people", suffix=f'ou=people,{DEFAULT_SUFFIX}') ++ e.run(no_errors) # no errors nor warnings are expected. ++ ++ # 4. Online import using backend name ou=people subtree ++ e = Importer(inst, errlog, "people") ++ e.run(no_suffix_on) ++ e.check_db() ++ ++ # 5. Online import using suffix name ou=people subtree ++ e = Importer(inst, errlog, "people", suffix=DEFAULT_SUFFIX) ++ e.run(no_suffix_on) ++ e.check_db() ++ ++ # 6. Stop the instance ++ inst.stop() ++ ++ # 7. Offline import using backend name ou=people subtree ++ e = Importer(inst, errlog, "people") ++ e.run(no_suffix_off) ++ e.check_db() ++ ++ # 8. Offline import using suffix name ou=people subtree ++ e = Importer(inst, errlog, "people", suffix=DEFAULT_SUFFIX) ++ e.run(no_suffix_off) ++ e.check_db() ++ ++ # 9. Generate ldif with a far away suffix ++ e = Importer(inst, errlog, "full") ++ people_ldif = e.ldif ++ e = Importer(inst, errlog, "far") ++ with open(e.ldif, "wt") as fout: ++ with open(people_ldif, "rt") as fin: ++ # Copy version ++ line = fin.readline() ++ fout.write(line) ++ line = fin.readline() ++ fout.write(line) ++ # Generate fake entries ++ for idx in range(10): ++ fout.write(f"dn: uid=id{idx},dc=foo\nobjectclasses: extensibleObject\n\n") ++ for line in iter(fin.readline, ''): ++ fout.write(line) ++ ++ os.chmod(e.ldif, 0o644) ++ ++ # 10. Offline import using backend name ou=people subtree ++ e.run(far_suffix_off) ++ e.check_db() ++ ++ # 11. Offline import using suffix name ou=people subtree ++ e = Importer(inst, errlog, "far", suffix=DEFAULT_SUFFIX) ++ e.run(far_suffix_with_suffix_off) ++ e.check_db() ++ ++ # 12. Start the instance ++ inst.start() ++ ++ # 13. Online import using backend name ou=people subtree ++ e = Importer(inst, errlog, "far") ++ e.run(far_suffix_on) ++ e.check_db() ++ ++ # 14. Online import using suffix name ou=people subtree ++ e = Importer(inst, errlog, "far", suffix=DEFAULT_SUFFIX) ++ e.run(far_suffix_with_suffix_on) ++ e.check_db() + + + if __name__ == '__main__': +diff --git a/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py b/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py +index 5aac40d2b..44804bd1c 100644 +--- a/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py ++++ b/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py +@@ -44,7 +44,10 @@ def test_fixup_task_limit(topo): + group = groups.create(properties={'cn': 'test'}) + + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) +- for idx in range(400): ++ # Turn on access log buffering to speed up user creation ++ buffering = topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logbuffering') ++ topo.standalone.config.set('nsslapd-accesslog-logbuffering', 'on') ++ for idx in range(6000): + user = users.create(properties={ + 'uid': 'testuser%s' % idx, + 'cn' : 'testuser%s' % idx, +@@ -55,6 +58,9 @@ def test_fixup_task_limit(topo): + }) + group.add('member', user.dn) + ++ # Restore access log buffering ++ topo.standalone.config.set('nsslapd-accesslog-logbuffering', buffering) ++ + # Configure memberOf plugin + memberof = MemberOfPlugin(topo.standalone) + memberof.enable() +diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py +index 6eaee08a4..a2be413c8 100644 +--- a/dirsrvtests/tests/suites/plugins/attruniq_test.py ++++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py +@@ -84,14 +84,23 @@ def containers(topology_st, request): + def attruniq(topology_st, request): + log.info('Setup attribute uniqueness plugin') + attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config") +- attruniq.create(properties={'cn': 'attruniq'}) +- attruniq.add_unique_attribute('cn') ++ ++ if attruniq.exists(): ++ attruniq.delete() ++ topology_st.standalone.restart() ++ ++ attruniq.create(properties={ ++ 'cn': 'attruniq', ++ 'uniqueness-attribute-name': 'cn', ++ 'uniqueness-subtrees': 'cn=config', ++ 'nsslapd-pluginEnabled': 'on' ++ }) + topology_st.standalone.restart() + + def fin(): + if attruniq.exists(): +- attruniq.disable() + attruniq.delete() ++ topology_st.standalone.restart() + + request.addfinalizer(fin) + +@@ -250,8 +259,8 @@ def test_modrdn_attr_uniqueness(topology_st, attruniq): + group1 = groups.create(properties={'cn': 'group1'}) + group2 = groups.create(properties={'cn': 'group2'}) + +- attruniq.add_unique_attribute('mail') +- attruniq.add_unique_subtree(group2.dn) ++ attruniq.replace('uniqueness-attribute-name', 'mail') ++ attruniq.replace('uniqueness-subtrees', group2.dn) + attruniq.enable_all_subtrees() + log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"') + attruniq.enable() +@@ -274,9 +283,6 @@ def test_modrdn_attr_uniqueness(topology_st, attruniq): + assert 'attribute value already exist' in str(excinfo.value) + log.debug(excinfo.value) + +- log.debug('Move user2 to group1') +- user2.rename(f'uid={user2.rdn}', group1.dn) +- + user1.delete() + user2.delete() + +@@ -302,17 +308,11 @@ def test_multiple_attr_uniqueness(topology_st, attruniq): + 6. Should raise CONSTRAINT_VIOLATION + """ + +- try: +- attruniq.add_unique_attribute('mail') +- attruniq.add_unique_attribute('mailAlternateAddress') +- attruniq.add_unique_subtree(DEFAULT_SUFFIX) +- attruniq.enable_all_subtrees() +- log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"') +- attruniq.enable() +- except ldap.LDAPError as e: +- log.fatal('test_multiple_attribute_uniqueness: Failed to configure plugin for "mail": error {}'.format(e.args[0]['desc'])) +- assert False +- ++ attruniq.replace('uniqueness-attribute-name', ['mail', 'mailAlternateAddress']) ++ attruniq.replace('uniqueness-subtrees', DEFAULT_SUFFIX) ++ attruniq.enable_all_subtrees() ++ log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"') ++ attruniq.enable() + topology_st.standalone.restart() + + users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX) +@@ -383,8 +383,9 @@ def test_exclude_subtrees(topology_st, attruniq): + 16. Success + 17. Success + """ +- attruniq.add_unique_attribute('telephonenumber') +- attruniq.add_unique_subtree(DEFAULT_SUFFIX) ++ # Replace dummy config with actual test config ++ attruniq.replace('uniqueness-attribute-name', 'telephonenumber') ++ attruniq.replace('uniqueness-subtrees', DEFAULT_SUFFIX) + attruniq.enable_all_subtrees() + attruniq.enable() + topology_st.standalone.restart() +@@ -517,10 +518,18 @@ def test_matchingrule_attr(topology_st): + """ + + inst = topology_st.standalone +- + attruniq = AttributeUniquenessPlugin(inst, + dn="cn=attribute uniqueness,cn=plugins,cn=config") +- attruniq.add_unique_attribute('cn:CaseExactMatch:') ++ ++ if attruniq.exists(): ++ attruniq.delete() ++ inst.restart() ++ ++ attruniq.create(properties={ ++ 'cn': 'attribute uniqueness', ++ 'uniqueness-attribute-name': 'cn:CaseExactMatch:', ++ 'uniqueness-subtrees': DEFAULT_SUFFIX ++ }) + attruniq.enable_all_subtrees() + attruniq.enable() + inst.restart() +@@ -595,7 +604,7 @@ def test_one_container_add(topology_st, attruniq, containers, active_user_1): + active_2.delete() + + log.info('Setup attribute uniqueness plugin for "cn" attribute') +- attruniq.add_unique_subtree(ACTIVE_DN) ++ attruniq.replace('uniqueness-subtrees', ACTIVE_DN) + attruniq.enable() + topology_st.standalone.restart() + +@@ -628,7 +637,7 @@ def test_one_container_mod(topology_st, attruniq, containers, + """ + + log.info('Setup attribute uniqueness plugin for "cn" attribute') +- attruniq.add_unique_subtree(ACTIVE_DN) ++ attruniq.replace('uniqueness-subtrees', ACTIVE_DN) + attruniq.enable() + topology_st.standalone.restart() + +@@ -656,7 +665,7 @@ def test_one_container_modrdn(topology_st, attruniq, containers, + """ + + log.info('Setup attribute uniqueness plugin for "cn" attribute') +- attruniq.add_unique_subtree(ACTIVE_DN) ++ attruniq.replace('uniqueness-subtrees', ACTIVE_DN) + attruniq.enable() + topology_st.standalone.restart() + +@@ -690,8 +699,7 @@ def test_multiple_containers_add(topology_st, attruniq, containers, + """ + + log.info('Setup attribute uniqueness plugin for "cn" attribute') +- attruniq.add_unique_subtree(ACTIVE_DN) +- attruniq.add_unique_subtree(STAGE_DN) ++ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN]) + attruniq.enable() + topology_st.standalone.restart() + +@@ -789,8 +797,7 @@ def test_multiple_containers_mod(topology_st, attruniq, containers, + """ + + log.info('Setup attribute uniqueness plugin for "cn" attribute') +- attruniq.add_unique_subtree(ACTIVE_DN) +- attruniq.add_unique_subtree(STAGE_DN) ++ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN]) + attruniq.enable() + topology_st.standalone.restart() + +@@ -874,8 +881,8 @@ def test_multiple_containers_modrdn(topology_st, attruniq, containers, + """ + + log.info('Setup attribute uniqueness plugin for "cn" attribute') +- attruniq.add_unique_subtree(ACTIVE_DN) +- attruniq.add_unique_subtree(STAGE_DN) ++ # Replace dummy subtree with actual test subtrees ++ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN]) + attruniq.enable() + topology_st.standalone.restart() + +@@ -993,7 +1000,10 @@ def test_invalid_config_missing_attr_name(topology_st): + _config_file(topology_st, action='save') + + attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config") +- attruniq.create(properties={'cn': 'attruniq'}) ++ attruniq.create(properties={ ++ 'cn': 'attruniq', ++ 'uniqueness-subtrees': DEFAULT_SUFFIX ++ }) + attruniq.enable() + + topology_st.standalone.errorlog_file = open(topology_st.standalone.errlog, "r") +@@ -1040,9 +1050,11 @@ def test_invalid_config_invalid_subtree(topology_st): + _config_file(topology_st, action='save') + + attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config") +- attruniq.create(properties={'cn': 'attruniq'}) +- attruniq.add_unique_attribute('cn') +- attruniq.add_unique_subtree('invalid_subtree') ++ attruniq.create(properties={ ++ 'cn': 'attruniq', ++ 'uniqueness-attribute-name': 'cn', ++ 'uniqueness-subtrees': 'invalid_subtree' ++ }) + attruniq.enable() + + topology_st.standalone.errorlog_file = open(topology_st.standalone.errlog, "r") +diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py +index ba1ffcc9c..db5140b0b 100644 +--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py ++++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py +@@ -1221,6 +1221,10 @@ def test_rid_starting_with_0(topo_m2, request): + for replica,rid in zip(replicas, ['010', '020']): + replica.replace('nsDS5ReplicaId', rid) + ++ # Restart required - replica IDs are loaded at startup and cached in memory ++ S1.restart() ++ S2.restart() ++ + # Restore replica id in finalizer + def fin(): + for replica,rid in zip(replicas, ['1', '2']): +diff --git a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py +index 665fcb96f..b2b2d25a2 100644 +--- a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py ++++ b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py +@@ -369,11 +369,13 @@ def test_replication_log_monitoring_multi_suffix(topo_m4): + repl.ensure_agreement(s1, s2) + repl.ensure_agreement(s2, s1) + +- # Allow initial topology to settle before capturing metrics ++ # Wait for all the setup replication to settle, then clear the logs + for suffix in all_suffixes: + repl = ReplicationManager(suffix) +- repl.test_replication_topology(topo_m4) +- ++ for s1 in suppliers: ++ for s2 in suppliers: ++ if s1 != s2: ++ repl.wait_for_replication(s1, s2) + for supplier in suppliers: + supplier.deleteAccessLogs(restart=True) + +diff --git a/dirsrvtests/tests/suites/webui/database/database_test.py b/dirsrvtests/tests/suites/webui/database/database_test.py +index ef105d262..05ddb6b00 100644 +--- a/dirsrvtests/tests/suites/webui/database/database_test.py ++++ b/dirsrvtests/tests/suites/webui/database/database_test.py +@@ -138,6 +138,7 @@ def test_chaining_configuration_availability(topology_st, page, browser_name): + log.info('Click on Chaining Configuration and check if element is loaded.') + frame.get_by_role('tab', name='Database', exact=True).click() + frame.locator('#chaining-config').click() ++ frame.locator('#chaining-page').wait_for() + frame.locator('#defSizeLimit').wait_for() + assert frame.locator('#defSizeLimit').is_visible() + +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c +index e3b7e5783..b1a29ff7f 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c +@@ -1080,7 +1080,9 @@ ldbm_back_modrdn(Slapi_PBlock *pb) + } + } + } +- if (slapi_sdn_get_dn(dn_newsuperiordn) != NULL) { ++ /* Only update parent if we're actually moving to a NEW parent (not the same parent) */ ++ if (slapi_sdn_get_dn(dn_newsuperiordn) != NULL && ++ slapi_sdn_compare(dn_newsuperiordn, &dn_parentdn) != 0) { + /* Push out the db modifications from the parent entry */ + retval = modify_update_all(be, pb, &parent_modify_context, &txn); + if (DBI_RC_RETRY == retval) { +@@ -1335,11 +1337,6 @@ ldbm_back_modrdn(Slapi_PBlock *pb) + goto common_return; + + error_return: +- /* Revert the caches if this is the parent operation */ +- if (parent_op && betxn_callback_fails) { +- revert_cache(inst, &parent_time); +- } +- + /* result already sent above - just free stuff */ + if (postentry) { + slapi_entry_free(postentry); +@@ -1417,13 +1414,6 @@ error_return: + slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); + } + slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); +- +- /* As it is a BETXN plugin failure then +- * revert the caches if this is the parent operation +- */ +- if (parent_op) { +- revert_cache(inst, &parent_time); +- } + } + retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN); + +@@ -1437,6 +1427,15 @@ error_return: + } + } + ++ /* Revert the caches if this is the parent operation and cache modifications were made. ++ * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed, ++ * so we should only revert if we got past that point (i.e., BETXN POST plugin failures). ++ * For BETXN PRE failures, no cache modifications were made to parent/newparent entries. ++ */ ++ if (parent_op && betxn_callback_fails && postentry) { ++ revert_cache(inst, &parent_time); ++ } ++ + common_return: + + /* result code could be used in the bepost plugin functions. */ +@@ -1482,12 +1481,22 @@ common_return: + "operation failed, the target entry is cleared from dncache (%s)\n", slapi_entry_get_dn(ec->ep_entry)); + CACHE_REMOVE(&inst->inst_dncache, bdn); + CACHE_RETURN(&inst->inst_dncache, &bdn); ++ ++ /* Also remove ec from entry cache and free it since the operation failed */ ++ if (inst && cache_is_in_cache(&inst->inst_cache, ec)) { ++ CACHE_REMOVE(&inst->inst_cache, ec); ++ CACHE_RETURN(&inst->inst_cache, &ec); ++ } else { ++ /* ec was not in cache, just free it */ ++ backentry_free(&ec); ++ } ++ ec = NULL; + } + + if (ec && inst) { + CACHE_RETURN(&inst->inst_cache, &ec); ++ ec = NULL; + } +- ec = NULL; + } + + if (inst) { +@@ -1817,6 +1826,8 @@ moddn_get_newdn(Slapi_PBlock *pb, Slapi_DN *dn_olddn, Slapi_DN *dn_newrdn, Slapi + + /* + * Return the entries to the cache. ++ * For the original entry 'e', we should NOT remove it from cache on failure, ++ * as it's still a valid entry in the directory. + */ + static void + moddn_unlock_and_return_entry( +@@ -1825,12 +1836,9 @@ moddn_unlock_and_return_entry( + { + ldbm_instance *inst = (ldbm_instance *)be->be_instance_info; + +- /* Something bad happened so we should give back all the entries */ ++ /* Unlock and return the entry to the cache */ + if (*targetentry != NULL) { + cache_unlock_entry(&inst->inst_cache, *targetentry); +- if (cache_is_in_cache(&inst->inst_cache, *targetentry)) { +- CACHE_REMOVE(&inst->inst_cache, *targetentry); +- } + CACHE_RETURN(&inst->inst_cache, targetentry); + *targetentry = NULL; + } +-- +2.52.0 + diff --git a/SOURCES/0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch b/SOURCES/0019-Issue-6897-Fix-disk-monitoring-test-failures-and-imp.patch deleted file mode 100644 index e9a2e57..0000000 --- a/SOURCES/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/SOURCES/0019-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch b/SOURCES/0019-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch new file mode 100644 index 0000000..9036d72 --- /dev/null +++ b/SOURCES/0019-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch @@ -0,0 +1,57 @@ +From 04a0f5560ea741972b9c264af23b377893ab7133 Mon Sep 17 00:00:00 2001 +From: Akshay Adhikari +Date: Thu, 5 Feb 2026 15:41:09 +0530 +Subject: [PATCH] Issue 7076 - Fix revert_cache() never called in modrdn + (#7220) + +Description: The postentry check in PR #7077 was broken - postentry is always NULL +at that point, fixed by removing the check. + +Relates: #7076 + +Reviewed by: @vashirov, @mreynolds389, @droideck (Thanks!) +--- + ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c +index b1a29ff7f..36377fc01 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c +@@ -103,6 +103,7 @@ ldbm_back_modrdn(Slapi_PBlock *pb) + Connection *pb_conn = NULL; + int32_t parent_op = 0; + int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */ ++ int32_t cache_mod_phase = 0; /* set when we reach the cache modification phase */ + struct timespec parent_time; + Slapi_Mods *smods_add_rdn = NULL; + +@@ -1229,6 +1230,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb) + goto error_return; + } + ++ /* We're now past the BETXN PRE phase and entering the cache modification phase */ ++ cache_mod_phase = 1; + postentry = slapi_entry_dup(ec->ep_entry); + + if (parententry != NULL) { +@@ -1427,12 +1430,11 @@ error_return: + } + } + +- /* Revert the caches if this is the parent operation and cache modifications were made. +- * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed, +- * so we should only revert if we got past that point (i.e., BETXN POST plugin failures). +- * For BETXN PRE failures, no cache modifications were made to parent/newparent entries. ++ /* Revert the caches if this is the parent operation AND we reached the ++ * cache modification phase. If BETXN PRE fails, cache_mod_phase is 0 ++ * and we don't need to revert since no cache modifications were made. + */ +- if (parent_op && betxn_callback_fails && postentry) { ++ if (parent_op && betxn_callback_fails && cache_mod_phase) { + revert_cache(inst, &parent_time); + } + +-- +2.52.0 + diff --git a/SOURCES/0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch b/SOURCES/0020-Issue-6339-Address-Coverity-scan-issues-in-memberof-.patch deleted file mode 100644 index fd7bf88..0000000 --- a/SOURCES/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/SOURCES/0020-Issue-6947-Fix-health_system_indexes_test.py.patch b/SOURCES/0020-Issue-6947-Fix-health_system_indexes_test.py.patch new file mode 100644 index 0000000..6d15057 --- /dev/null +++ b/SOURCES/0020-Issue-6947-Fix-health_system_indexes_test.py.patch @@ -0,0 +1,24 @@ +From fb8acf6141b069e250be3821c1e70ee7ed448720 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Wed, 11 Feb 2026 19:53:44 +0100 +Subject: [PATCH] Issue 6947 - Fix health_system_indexes_test.py + +--- + .../tests/suites/healthcheck/health_system_indexes_test.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 4cd4d0c70..3b3651b38 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -99,6 +99,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searc + "memberof", + ] + args.dry_run = False ++ args.exclude_check = [] + + # If we are using BDB as a backend, we will get error DSBLE0006 on new versions + if ( +-- +2.52.0 + diff --git a/SOURCES/0021-Issue-6468-CLI-Fix-default-error-log-level.patch b/SOURCES/0021-Issue-6468-CLI-Fix-default-error-log-level.patch deleted file mode 100644 index 884e524..0000000 --- a/SOURCES/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/SOURCES/0021-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch b/SOURCES/0021-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch new file mode 100644 index 0000000..777be86 --- /dev/null +++ b/SOURCES/0021-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch @@ -0,0 +1,71 @@ +From 8761b28fd2f2cf3d2e2119bdf1b348bc7d2d1834 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Mon, 9 Feb 2026 13:18:09 +0100 +Subject: [PATCH] Issue 7121 - (2nd) LeakSanitizer: various leaks during + replication (#7212) + +Bug Description: +With the previous fix 75e0e487545893a7b0d83f94f9264c10f8bb0353 applied, +server can crash in ber_bvcpy. + +``` +Program terminated with signal SIGSEGV, Segmentation fault. +#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47 +47 len = bvs->bv_len; +[Current thread is 1 (Thread 0x7f1db47fe640 (LWP 36576))] +(gdb) bt +#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47 +#1 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:40 +#2 slapi_value_set_berval (bval=0x7f1d00000000, value=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:322 +#3 slapi_value_set_berval (value=value@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000) at ldap/servers/slapd/value.c:317 +#4 0x00007f1e48b7d787 in value_init (v=v@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0) + at ldap/servers/slapd/value.c:179 +#5 0x00007f1e48b7d884 in value_new (bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0) at ldap/servers/slapd/value.c:158 +#6 0x00007f1e48b7ddb7 in slapi_value_dup (v=0x7f1d00000000) at ldap/servers/slapd/value.c:147 +#7 0x00007f1e48b7e262 in valueset_set_valueset (vs2=0x7f1d502b5218, vs1=0x7f1da2c5b358) at ldap/servers/slapd/valueset.c:1244 +#8 valueset_set_valueset (vs1=0x7f1da2c5b358, vs2=0x7f1d502b5218) at ldap/servers/slapd/valueset.c:1220 +#9 0x00007f1e48add4af in slapi_attr_dup (attr=0x7f1d502b51e0) at ldap/servers/slapd/attr.c:396 +#10 0x00007f1e48af0f60 in slapi_entry_dup (e=0x7f1da2c19000) at ldap/servers/slapd/entry.c:2036 +#11 0x00007f1e442c734e in ldbm_back_modify (pb=0x7f1da2c00000) at ldap/servers/slapd/back-ldbm/ldbm_modify.c:741 +#12 0x00007f1e48b30076 in op_shared_modify (pb=pb@entry=0x7f1da2c00000, pw_change=pw_change@entry=0, old_pw=0x0) + at ldap/servers/slapd/modify.c:1079 +#13 0x00007f1e48b30ced in do_modify (pb=pb@entry=0x7f1da2c00000) at ldap/servers/slapd/modify.c:377 +#14 0x000055e990e2fd1c in connection_dispatch_operation (pb=0x7f1da2c00000, op=, conn=) + at ldap/servers/slapd/connection.c:672 +#15 connection_threadmain (arg=) at ldap/servers/slapd/connection.c:1955 +#16 0x00007f1e48839bd4 in _pt_root (arg=0x7f1e439d9500) at pthreads/../../../../nspr/pr/src/pthreads/ptthread.c:191 +#17 0x00007f1e4868a19a in start_thread (arg=) at pthread_create.c:443 +#18 0x00007f1e4870f100 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81 +``` + +The fix changed from always setting `v_csnset = NULL` to only freeing it +inside the if-block. + +Fix Description: +Keep `csnset_free()` outside the if-block to handle all values, not just +those matching the condtion. + +Related: https://github.com/389ds/389-ds-base/issues/7121 + +Reviewed by: @progier389, @droideck (Thanks!) +--- + ldap/servers/slapd/entrywsi.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ldap/servers/slapd/entrywsi.c b/ldap/servers/slapd/entrywsi.c +index e1bdc1bab..0d044092d 100644 +--- a/ldap/servers/slapd/entrywsi.c ++++ b/ldap/servers/slapd/entrywsi.c +@@ -1185,8 +1185,8 @@ resolve_attribute_state_deleted_to_present(Slapi_Entry *e, Slapi_Attr *a, Slapi_ + if ((csn_compare(vucsn, deletedcsn) >= 0) || + value_distinguished_at_csn(e, a, valuestoupdate[i], deletedcsn)) { + entry_deleted_value_to_present_value(a, valuestoupdate[i]); +- csnset_free(&valuestoupdate[i]->v_csnset); + } ++ csnset_free(&valuestoupdate[i]->v_csnset); + } + } + } +-- +2.52.0 + diff --git a/SOURCES/0022-Issue-7150-Compressed-access-log-rotations-skipped-a.patch b/SOURCES/0022-Issue-7150-Compressed-access-log-rotations-skipped-a.patch new file mode 100644 index 0000000..5f7b711 --- /dev/null +++ b/SOURCES/0022-Issue-7150-Compressed-access-log-rotations-skipped-a.patch @@ -0,0 +1,551 @@ +From 86b15d688949197a9efe3d5fc7a7eadcdd46e115 Mon Sep 17 00:00:00 2001 +From: Simon Pichugin +Date: Tue, 16 Dec 2025 15:48:35 -0800 +Subject: [PATCH] Issue 7150 - Compressed access log rotations skipped, + accesslog-list out of sync (#7151) + +Description: Accept `.gz`-suffixed rotated log filenames when +rebuilding rotation info and checking previous logs, preventing +compressed rotations from being dropped from the internal list. + +Add regression tests to stress log rotation with compression, +verify `nsslapd-accesslog-list` stays in sync, and guard against +crashes when flushing buffered logs during rotation. +Minor doc fix in test. + +Fixes: https://github.com/389ds/389-ds-base/issues/7150 + +Reviewed by: @progier389 (Thanks!) +--- + .../suites/logging/log_flush_rotation_test.py | 341 +++++++++++++++++- + ldap/servers/slapd/log.c | 99 +++-- + 2 files changed, 402 insertions(+), 38 deletions(-) + +diff --git a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py +index b33a622e1..864ba9c5d 100644 +--- a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py ++++ b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py +@@ -6,6 +6,7 @@ + # See LICENSE for details. + # --- END COPYRIGHT BLOCK --- + # ++import glob + import os + import logging + import time +@@ -13,14 +14,351 @@ import pytest + from lib389._constants import DEFAULT_SUFFIX, PW_DM + from lib389.tasks import ImportTask + from lib389.idm.user import UserAccounts ++from lib389.idm.domain import Domain ++from lib389.idm.directorymanager import DirectoryManager + from lib389.topologies import topology_st as topo + + + log = logging.getLogger(__name__) + + ++def remove_rotated_access_logs(inst): ++ """ ++ Remove all rotated access log files to start fresh for each test. ++ This prevents log files from previous tests affecting current test results. ++ """ ++ log_dir = inst.get_log_dir() ++ patterns = [ ++ f'{log_dir}/access.2*', # Uncompressed rotated logs ++ f'{log_dir}/access.*.gz', # Compressed rotated logs ++ ] ++ for pattern in patterns: ++ for log_file in glob.glob(pattern): ++ try: ++ os.remove(log_file) ++ log.info(f"Removed old log file: {log_file}") ++ except OSError as e: ++ log.warning(f"Could not remove {log_file}: {e}") ++ ++ ++def reset_access_log_config(inst): ++ """ ++ Reset access log configuration to default values. ++ """ ++ inst.config.set('nsslapd-accesslog-compress', 'off') ++ inst.config.set('nsslapd-accesslog-maxlogsize', '100') ++ inst.config.set('nsslapd-accesslog-maxlogsperdir', '10') ++ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off') ++ inst.config.set('nsslapd-accesslog-logbuffering', 'on') ++ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1') ++ inst.config.set('nsslapd-accesslog-logminfreediskspace', '5') ++ ++ ++def generate_heavy_load(inst, suffix, iterations=50): ++ """ ++ Generate heavy LDAP load to fill access log quickly. ++ Performs multiple operations: searches, modifies, binds to populate logs. ++ """ ++ for i in range(iterations): ++ suffix.replace('description', f'iteration_{i}') ++ suffix.get_attr_val('description') ++ ++ ++def count_access_logs(log_dir, compressed_only=False): ++ """ ++ Count access log files in the log directory. ++ Returns count of rotated access logs (not including the active 'access' file). ++ """ ++ if compressed_only: ++ pattern = f'{log_dir}/access.*.gz' ++ else: ++ pattern = f'{log_dir}/access.2*' ++ log_files = glob.glob(pattern) ++ return len(log_files) ++ ++ ++def test_log_pileup_with_compression(topo): ++ """Test that log rotation properly deletes old logs when compression is enabled. ++ ++ :id: fa1bfce8-b6d3-4520-a0a8-bead14fa5838 ++ :setup: Standalone Instance ++ :steps: ++ 1. Clean up existing rotated logs and reset configuration ++ 2. Enable access log compression ++ 3. Set strict log limits (small maxlogsperdir) ++ 4. Disable log expiration to test count-based deletion ++ 5. Generate heavy load to create many log rotations ++ 6. Verify log count does not exceed maxlogsperdir limit ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Log count should be at or below maxlogsperdir + small buffer ++ """ ++ ++ inst = topo.standalone ++ suffix = Domain(inst, DEFAULT_SUFFIX) ++ log_dir = inst.get_log_dir() ++ ++ # Clean up before test ++ remove_rotated_access_logs(inst) ++ reset_access_log_config(inst) ++ inst.restart() ++ ++ max_logs = 5 ++ inst.config.set('nsslapd-accesslog-compress', 'on') ++ inst.config.set('nsslapd-accesslog-maxlogsperdir', str(max_logs)) ++ inst.config.set('nsslapd-accesslog-maxlogsize', '1') # 1MB to trigger rotation ++ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off') ++ inst.config.set('nsslapd-accesslog-logbuffering', 'off') ++ ++ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1') ++ ++ inst.config.set('nsslapd-accesslog-logminfreediskspace', '5') ++ ++ inst.restart() ++ time.sleep(2) ++ ++ target_logs = max_logs * 3 ++ for i in range(target_logs): ++ log.info(f"Generating load for log rotation {i+1}/{target_logs}") ++ generate_heavy_load(inst, suffix, iterations=150) ++ time.sleep(1) # Wait for rotation ++ ++ time.sleep(3) ++ ++ logs_on_disk = count_access_logs(log_dir) ++ log.info(f"Configured maxlogsperdir: {max_logs}") ++ log.info(f"Actual rotated logs on disk: {logs_on_disk}") ++ ++ all_access_logs = glob.glob(f'{log_dir}/access*') ++ log.info(f"All access log files: {all_access_logs}") ++ ++ max_allowed = max_logs + 2 ++ assert logs_on_disk <= max_allowed, ( ++ f"Log rotation failed to delete old files! " ++ f"Expected at most {max_allowed} rotated logs (maxlogsperdir={max_logs} + 2 buffer), " ++ f"but found {logs_on_disk}. The server has lost track of the file list." ++ ) ++ ++ ++@pytest.mark.parametrize("compress_enabled", ["on", "off"]) ++def test_accesslog_list_mismatch(topo, compress_enabled): ++ """Test that nsslapd-accesslog-list stays synchronized with actual log files. ++ ++ :id: 0a8a46a6-cae7-43bd-8b64-5e3481480cd3 ++ :parametrized: yes ++ :setup: Standalone Instance ++ :steps: ++ 1. Clean up existing rotated logs and reset configuration ++ 2. Configure log rotation with compression enabled/disabled ++ 3. Generate activity to trigger multiple rotations ++ 4. Get the nsslapd-accesslog-list attribute ++ 5. Compare with actual files on disk ++ 6. Verify they match (accounting for .gz extension when enabled) ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. The list attribute should match actual files on disk ++ """ ++ ++ inst = topo.standalone ++ suffix = Domain(inst, DEFAULT_SUFFIX) ++ log_dir = inst.get_log_dir() ++ compression_on = compress_enabled == "on" ++ ++ # Clean up before test ++ remove_rotated_access_logs(inst) ++ reset_access_log_config(inst) ++ inst.restart() ++ ++ inst.config.set('nsslapd-accesslog-compress', compress_enabled) ++ inst.config.set('nsslapd-accesslog-maxlogsize', '1') ++ inst.config.set('nsslapd-accesslog-maxlogsperdir', '10') ++ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off') ++ inst.config.set('nsslapd-accesslog-logbuffering', 'off') ++ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1') ++ ++ inst.restart() ++ time.sleep(2) ++ ++ for i in range(15): ++ suffix_note = "(no compression)" if not compression_on else "" ++ log.info(f"Generating load for rotation {i+1}/15 {suffix_note}") ++ generate_heavy_load(inst, suffix, iterations=150) ++ time.sleep(1) ++ ++ time.sleep(3) ++ ++ accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list') ++ log.info(f"nsslapd-accesslog-list entries (compress={compress_enabled}): {len(accesslog_list)}") ++ log.info(f"nsslapd-accesslog-list (compress={compress_enabled}): {accesslog_list}") ++ ++ disk_files = glob.glob(f'{log_dir}/access.2*') ++ log.info(f"Actual files on disk (compress={compress_enabled}): {len(disk_files)}") ++ log.info(f"Disk files (compress={compress_enabled}): {disk_files}") ++ ++ disk_files_for_compare = set() ++ for fpath in disk_files: ++ if compression_on and fpath.endswith('.gz'): ++ disk_files_for_compare.add(fpath[:-3]) ++ else: ++ disk_files_for_compare.add(fpath) ++ ++ list_files_set = set(accesslog_list) ++ missing_from_disk = list_files_set - disk_files_for_compare ++ extra_on_disk = disk_files_for_compare - list_files_set ++ ++ if missing_from_disk: ++ log.error( ++ f"[compress={compress_enabled}] Files in list but NOT on disk: {missing_from_disk}" ++ ) ++ if extra_on_disk: ++ log.warning( ++ f"[compress={compress_enabled}] Files on disk but NOT in list: {extra_on_disk}" ++ ) ++ ++ assert not missing_from_disk, ( ++ f"nsslapd-accesslog-list mismatch (compress={compress_enabled})! " ++ f"Files listed but missing from disk: {missing_from_disk}. " ++ f"This indicates the server's internal list is out of sync with actual files." ++ ) ++ ++ if len(extra_on_disk) > 2: ++ log.warning( ++ f"Potential log tracking issue (compress={compress_enabled}): " ++ f"{len(extra_on_disk)} files on disk are not tracked in the accesslog-list: " ++ f"{extra_on_disk}" ++ ) ++ ++ ++def test_accesslog_list_mixed_compression(topo): ++ """Test that nsslapd-accesslog-list correctly tracks both compressed and uncompressed logs. ++ ++ :id: 11b088cd-23be-407d-ad16-4ce2e12da09e ++ :setup: Standalone Instance ++ :steps: ++ 1. Clean up existing rotated logs and reset configuration ++ 2. Create rotated logs with compression OFF ++ 3. Enable compression and create more rotated logs ++ 4. Get the nsslapd-accesslog-list attribute ++ 5. Compare with actual files on disk ++ 6. Verify all files are correctly tracked (uncompressed and compressed) ++ :expectedresults: ++ 1. Success ++ 2. Success - uncompressed rotated logs created ++ 3. Success - compressed rotated logs created ++ 4. Success ++ 5. Success ++ 6. The list should contain base filenames (without .gz) that ++ correspond to files on disk (either as-is or with .gz suffix) ++ """ ++ ++ inst = topo.standalone ++ suffix = Domain(inst, DEFAULT_SUFFIX) ++ log_dir = inst.get_log_dir() ++ ++ # Clean up before test ++ remove_rotated_access_logs(inst) ++ reset_access_log_config(inst) ++ inst.restart() ++ ++ inst.config.set('nsslapd-accesslog-compress', 'off') ++ inst.config.set('nsslapd-accesslog-maxlogsize', '1') ++ inst.config.set('nsslapd-accesslog-maxlogsperdir', '20') ++ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off') ++ inst.config.set('nsslapd-accesslog-logbuffering', 'off') ++ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1') ++ ++ inst.restart() ++ time.sleep(2) ++ ++ for i in range(15): ++ log.info(f"Generating load for uncompressed rotation {i+1}/15") ++ generate_heavy_load(inst, suffix, iterations=150) ++ time.sleep(1) ++ ++ time.sleep(2) ++ ++ # Check what we have so far ++ uncompressed_files = glob.glob(f'{log_dir}/access.2*') ++ log.info(f"Files on disk after uncompressed phase: {uncompressed_files}") ++ ++ inst.config.set('nsslapd-accesslog-compress', 'on') ++ inst.restart() ++ time.sleep(2) ++ ++ for i in range(15): ++ log.info(f"Generating load for compressed rotation {i+1}/15") ++ generate_heavy_load(inst, suffix, iterations=150) ++ time.sleep(1) ++ ++ time.sleep(3) ++ ++ accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list') ++ ++ disk_files = glob.glob(f'{log_dir}/access.2*') ++ ++ log.info(f"nsslapd-accesslog-list entries: {len(accesslog_list)}") ++ log.info(f"nsslapd-accesslog-list: {sorted(accesslog_list)}") ++ log.info(f"Actual files on disk: {len(disk_files)}") ++ log.info(f"Disk files: {sorted(disk_files)}") ++ ++ compressed_on_disk = [f for f in disk_files if f.endswith('.gz')] ++ uncompressed_on_disk = [f for f in disk_files if not f.endswith('.gz')] ++ log.info(f"Compressed files on disk: {compressed_on_disk}") ++ log.info(f"Uncompressed files on disk: {uncompressed_on_disk}") ++ ++ list_files_set = set(accesslog_list) ++ ++ disk_files_base = set() ++ for fpath in disk_files: ++ if fpath.endswith('.gz'): ++ disk_files_base.add(fpath[:-3]) # Strip .gz ++ else: ++ disk_files_base.add(fpath) ++ ++ missing_from_disk = list_files_set - disk_files_base ++ ++ extra_on_disk = disk_files_base - list_files_set ++ ++ if missing_from_disk: ++ log.error(f"Files in list but NOT on disk: {missing_from_disk}") ++ if extra_on_disk: ++ log.warning(f"Files on disk but NOT in list: {extra_on_disk}") ++ ++ assert not missing_from_disk, ( ++ f"nsslapd-accesslog-list contains stale entries! " ++ f"Files in list but not on disk (as base or .gz): {missing_from_disk}" ++ ) ++ ++ for list_file in accesslog_list: ++ exists_uncompressed = os.path.exists(list_file) ++ exists_compressed = os.path.exists(list_file + '.gz') ++ assert exists_uncompressed or exists_compressed, ( ++ f"File in accesslog-list does not exist on disk: {list_file} " ++ f"(checked both {list_file} and {list_file}.gz)" ++ ) ++ if exists_compressed and not exists_uncompressed: ++ log.info(f" {list_file} -> exists as .gz (compressed)") ++ elif exists_uncompressed: ++ log.info(f" {list_file} -> exists (uncompressed)") ++ ++ if len(extra_on_disk) > 1: ++ log.warning( ++ f"Some files on disk are not tracked in accesslog-list: {extra_on_disk}" ++ ) ++ ++ log.info("Mixed compression test completed successfully") ++ ++ + def test_log_flush_and_rotation_crash(topo): +- """Make sure server does not crash whening flushing a buffer and rotating ++ """Make sure server does not crash when flushing a buffer and rotating + the log at the same time + + :id: d4b0af2f-48b2-45f5-ae8b-f06f692c3133 +@@ -36,6 +374,7 @@ def test_log_flush_and_rotation_crash(topo): + 3. Success + 4. Success + """ ++ # NOTE: This test is placed last as it may affect the suffix state. + + inst = topo.standalone + +diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c +index 6f57a3d9c..ca8d481e5 100644 +--- a/ldap/servers/slapd/log.c ++++ b/ldap/servers/slapd/log.c +@@ -135,6 +135,7 @@ static void vslapd_log_emergency_error(LOGFD fp, const char *msg, int locked); + static int get_syslog_loglevel(int loglevel); + static void log_external_libs_debug_openldap_print(char *buffer); + static int log__fix_rotationinfof(char *pathname); ++static int log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed); + + static int + get_syslog_loglevel(int loglevel) +@@ -410,7 +411,7 @@ g_log_init() + loginfo.log_security_fdes = NULL; + loginfo.log_security_file = NULL; + loginfo.log_securityinfo_file = NULL; +- loginfo.log_numof_access_logs = 1; ++ loginfo.log_numof_security_logs = 1; + loginfo.log_security_logchain = NULL; + loginfo.log_security_buffer = log_create_buffer(LOG_BUFFER_MAXSIZE); + loginfo.log_security_compress = cfg->securitylog_compress; +@@ -3311,7 +3312,7 @@ log__open_accesslogfile(int logfile_state, int locked) + } + } else if (loginfo.log_access_compress) { + if (compress_log_file(newfile, loginfo.log_access_mode) != 0) { +- slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile", ++ slapi_log_err(SLAPI_LOG_ERR, "log__open_accesslogfile", + "failed to compress rotated access log (%s)\n", + newfile); + } else { +@@ -4710,6 +4711,50 @@ log__delete_rotated_logs() + loginfo.log_error_logchain = NULL; + } + ++/* ++ * log__validate_rotated_logname ++ * ++ * Validates that a log filename timestamp suffix matches the expected format: ++ * YYYYMMDD-HHMMSS (15 chars) or YYYYMMDD-HHMMSS.gz (18 chars) for compressed files. ++ * Uses regex pattern: ^[0-9]{8}-[0-9]{6}(\.gz)?$ ++ * ++ * \param timestamp_str The timestamp portion of the log filename (after the first '.') ++ * \param is_compressed Output parameter set to PR_TRUE if the file has .gz suffix ++ * \return 1 if valid, 0 if invalid ++ */ ++static int ++log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed) ++{ ++ Slapi_Regex *re = NULL; ++ char *re_error = NULL; ++ int rc = 0; ++ ++ /* Match YYYYMMDD-HHMMSS with optional .gz suffix */ ++ static const char *pattern = "^[0-9]{8}-[0-9]{6}(\\.gz)?$"; ++ ++ *is_compressed = PR_FALSE; ++ ++ re = slapi_re_comp(pattern, &re_error); ++ if (re == NULL) { ++ slapi_log_err(SLAPI_LOG_ERR, "log__validate_rotated_logname", ++ "Failed to compile regex: %s\n", re_error ? re_error : "unknown error"); ++ slapi_ch_free_string(&re_error); ++ return 0; ++ } ++ ++ rc = slapi_re_exec_nt(re, timestamp_str); ++ if (rc == 1) { ++ /* Check if compressed by looking for .gz suffix */ ++ size_t len = strlen(timestamp_str); ++ if (len >= 3 && strcmp(timestamp_str + len - 3, ".gz") == 0) { ++ *is_compressed = PR_TRUE; ++ } ++ } ++ ++ slapi_re_free(re); ++ return rc == 1 ? 1 : 0; ++} ++ + #define ERRORSLOG 1 + #define ACCESSLOG 2 + #define AUDITLOG 3 +@@ -4792,31 +4837,19 @@ log__fix_rotationinfof(char *pathname) + } + } else if (0 == strncmp(log_type, dirent->name, strlen(log_type)) && + (p = strchr(dirent->name, '.')) != NULL && +- NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 */ ++ NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */ + { + struct logfileinfo *logp; +- char *q; +- int ignoreit = 0; +- +- for (q = ++p; q && *q; q++) { +- if (*q != '-' && +- *q != '.' && /* .gz */ +- *q != 'g' && +- *q != 'z' && +- !isdigit(*q)) +- { +- ignoreit = 1; +- } +- } +- if (ignoreit || (q - p != 15)) { ++ PRBool is_compressed = PR_FALSE; ++ ++ /* Skip the '.' to get the timestamp portion */ ++ p++; ++ if (!log__validate_rotated_logname(p, &is_compressed)) { + continue; + } + logp = (struct logfileinfo *)slapi_ch_malloc(sizeof(struct logfileinfo)); + logp->l_ctime = log_reverse_convert_time(p); +- logp->l_compressed = PR_FALSE; +- if (strcmp(p + strlen(p) - 3, ".gz") == 0) { +- logp->l_compressed = PR_TRUE; +- } ++ logp->l_compressed = is_compressed; + PR_snprintf(rotated_log, rotated_log_len, "%s/%s", + logsdir, dirent->name); + +@@ -4982,23 +5015,15 @@ log__check_prevlogs(FILE *fp, char *pathname) + for (dirent = PR_ReadDir(dirptr, dirflags); dirent; + dirent = PR_ReadDir(dirptr, dirflags)) { + if (0 == strncmp(log_type, dirent->name, strlen(log_type)) && +- (p = strrchr(dirent->name, '.')) != NULL && +- NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 */ +- char *q; +- int ignoreit = 0; +- +- for (q = ++p; q && *q; q++) { +- if (*q != '-' && +- *q != '.' && /* .gz */ +- *q != 'g' && +- *q != 'z' && +- !isdigit(*q)) +- { +- ignoreit = 1; +- } +- } +- if (ignoreit || (q - p != 15)) ++ (p = strchr(dirent->name, '.')) != NULL && ++ NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */ ++ PRBool is_compressed = PR_FALSE; ++ ++ /* Skip the '.' to get the timestamp portion */ ++ p++; ++ if (!log__validate_rotated_logname(p, &is_compressed)) { + continue; ++ } + + fseek(fp, 0, SEEK_SET); + buf[BUFSIZ - 1] = '\0'; +-- +2.52.0 + diff --git a/SOURCES/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch b/SOURCES/0022-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch deleted file mode 100644 index 2f5675e..0000000 --- a/SOURCES/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/SOURCES/0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch b/SOURCES/0023-Issue-6181-RFE-Allow-system-to-manage-uid-gid-at-sta.patch deleted file mode 100644 index d208d39..0000000 --- a/SOURCES/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/SOURCES/0023-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch b/SOURCES/0023-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch new file mode 100644 index 0000000..d3cd410 --- /dev/null +++ b/SOURCES/0023-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch @@ -0,0 +1,93 @@ +From 524cae18721234e1bc8d958c89684008d62e6b30 Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Thu, 5 Feb 2026 15:33:08 +0000 +Subject: [PATCH] Issue 7224 - CI Test - Simplify + test_reserve_descriptor_validation (#7225) + +Description: +Previously, the test_reserve_descriptor_validation CItest calculated +the expected number of file descriptors based on backends, indexes, +SSL/FIPS mode, and compared it to the value returned by the server. +This approach is fragile, especially in FIPS mode. + +Fix: +The test has been updated to simply verify that the server corrects +the configured nsslapd-reservedescriptors value if it is set too low, +instead of calculating the expected total. + +Fixes: https://github.com/389ds/389-ds-base/issues/7224 + +Reviewed by: @bsimonova (Thank you) +--- + .../suites/resource_limits/fdlimits_test.py | 36 +++++++------------ + 1 file changed, 13 insertions(+), 23 deletions(-) + +diff --git a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py +index edcae28a5..9d8fdec52 100644 +--- a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py ++++ b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py +@@ -27,7 +27,7 @@ RESRV_FD_ATTR = "nsslapd-reservedescriptors" + GLOBAL_LIMIT = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + SYSTEMD_LIMIT = ensure_str(check_output("systemctl show -p LimitNOFILE dirsrv@standalone1".split(" ")).strip()).split('=')[1] + CUSTOM_VAL = str(int(SYSTEMD_LIMIT) - 10) +-RESRV_DESC_VAL = str(10) ++RESRV_DESC_VAL_LOW = 10 + TOO_HIGH_VAL = str(GLOBAL_LIMIT * 2) + TOO_HIGH_VAL2 = str(int(SYSTEMD_LIMIT) * 2) + TOO_LOW_VAL = "0" +@@ -86,40 +86,30 @@ def test_reserve_descriptor_validation(topology_st): + :id: 9bacdbcc-7754-4955-8a56-1d8c82bce274 + :setup: Standalone Instance + :steps: +- 1. Set attr nsslapd-reservedescriptors to a low value of RESRV_DESC_VAL (10) ++ 1. Set attr nsslapd-reservedescriptors to a low value (10) + 2. Verify low value has been set + 3. Restart instance (On restart the reservedescriptor attr will be validated) +- 4. Check updated value for nsslapd-reservedescriptors attr ++ 4. Verify corrected value for nsslapd-reservedescriptors > low value + :expectedresults: + 1. Success +- 2. A value of RESRV_DESC_VAL (10) is returned ++ 2. A value of RESRV_DESC_VAL_LOW (10) is returned + 3. Success +- 4. A value of STANDALONE_INST_RESRV_DESCS (55) is returned ++ 4. Corrected value for nsslapd-reservedescriptors > low value + """ + +- # Set nsslapd-reservedescriptors to a low value (RESRV_DESC_VAL:10) +- topology_st.standalone.config.set(RESRV_FD_ATTR, RESRV_DESC_VAL) +- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR) +- assert resrv_fd == RESRV_DESC_VAL ++ # Set nsslapd-reservedescriptors to a low value (10) ++ topology_st.standalone.config.set(RESRV_FD_ATTR, str(RESRV_DESC_VAL_LOW)) ++ resrv_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)) ++ assert resrv_fd == RESRV_DESC_VAL_LOW + + # An instance restart triggers a validation of the configured nsslapd-reservedescriptors attribute + topology_st.standalone.restart() + +- """ +- A standalone instance contains a single backend with default indexes +- so we only check these. TODO add tests for repl, chaining, PTA, SSL +- """ +- STANDALONE_INST_RESRV_DESCS = 20 # 20 = Reserve descriptor constant +- backends = Backends(topology_st.standalone) +- STANDALONE_INST_RESRV_DESCS += (len(backends.list()) * 4) # 4 = Backend descriptor constant +- for be in backends.list() : +- STANDALONE_INST_RESRV_DESCS += len(be.get_indexes().list()) +- +- # Varify reservedescriptors has been updated +- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR) +- assert resrv_fd == str(STANDALONE_INST_RESRV_DESCS) ++ # Get the corrected value ++ corrected_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)) ++ assert corrected_fd > RESRV_DESC_VAL_LOW + +- log.info("test_reserve_descriptor_validation PASSED") ++ log.info(f"test_reserve_descriptor_validation PASSED (corrected from {RESRV_DESC_VAL_LOW} to {corrected_fd})") + + @pytest.mark.skipif(ds_is_older("1.4.1.2"), reason="Not implemented") + def test_reserve_descriptors_high(topology_st): +-- +2.52.0 + diff --git a/SOURCES/0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch b/SOURCES/0024-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch deleted file mode 100644 index d3ec59d..0000000 --- a/SOURCES/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/SOURCES/0024-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch b/SOURCES/0024-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch new file mode 100644 index 0000000..3f6ae94 --- /dev/null +++ b/SOURCES/0024-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch @@ -0,0 +1,92 @@ +From 91e03f431d8ee0b1d316ebd7add232bf49360db2 Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Thu, 12 Feb 2026 10:42:09 +0000 +Subject: [PATCH] Issue 7231 - Sync repl tests fail in FIPS mode due to non + FIPS compliant crypto (#7232) + +Description: +Several sync_repl tests fail when running on a FIPS enabled system. The failures +are caused by the sync repl client (Sync_persist), using TLS options and ciphers +that are not FIPS compatible. + +Fix: +Update the sync repl client to use FIPS approved TLS version. + +Fixes: https://github.com/389ds/389-ds-base/issues/7231 + +Reviewed by: @progier389, @droideck (Thank you) +--- + .../tests/suites/syncrepl_plugin/basic_test.py | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py +index c22331eb5..1e9a530bb 100644 +--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py ++++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py +@@ -20,7 +20,7 @@ from lib389.idm.group import Groups + from lib389.topologies import topology_st as topology + from lib389.topologies import topology_m2 as topo_m2 + from lib389.paths import Paths +-from lib389.utils import ds_is_older ++from lib389.utils import ds_is_older, is_fips + from lib389.plugins import RetroChangelogPlugin, ContentSyncPlugin, AutoMembershipPlugin, MemberOfPlugin, MemberOfSharedConfig, AutoMembershipDefinitions, MEPTemplates, MEPConfigs, ManagedEntriesPlugin, MEPTemplate + from lib389._constants import * + +@@ -214,6 +214,12 @@ class Sync_persist(threading.Thread, ReconnectLDAPObject, SyncreplConsumer): + def run(self): + """Start a sync repl client""" + ldap_connection = TestSyncer(self.inst.toLDAPURL()) ++ ldap_connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND) ++ ldap_connection.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join(self.inst.get_config_dir(), "ca.crt")) ++ if is_fips(): ++ ldap_connection.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, ldap.OPT_X_TLS_PROTOCOL_TLS1_2) ++ ldap_connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0) ++ + ldap_connection.simple_bind_s('cn=directory manager', 'password') + ldap_search = ldap_connection.syncrepl_search( + "dc=example,dc=com", +@@ -253,6 +259,7 @@ def test_sync_repl_mep(topology, request): + 5. Success + """ + inst = topology[0] ++ inst.enable_tls() + + # Enable/configure retroCL + plugin = RetroChangelogPlugin(inst) +@@ -338,6 +345,7 @@ def test_sync_repl_cookie(topology, init_sync_repl_plugins, request): + 5.: succeeds + """ + inst = topology[0] ++ inst.enable_tls() + + # create a sync repl client and wait 5 seconds to be sure it is running + sync_repl = Sync_persist(inst) +@@ -404,6 +412,8 @@ def test_sync_repl_cookie_add_del(topology, init_sync_repl_plugins, request): + 6.: succeeds + """ + inst = topology[0] ++ inst.enable_tls() ++ + # create a sync repl client and wait 5 seconds to be sure it is running + sync_repl = Sync_persist(inst) + sync_repl.start() +@@ -547,6 +557,7 @@ def test_sync_repl_cenotaph(topo_m2, request): + 5. Should succeeds + """ + m1 = topo_m2.ms["supplier1"] ++ m1.enable_tls() + # Enable/configure retroCL + plugin = RetroChangelogPlugin(m1) + plugin.disable() +@@ -605,7 +616,7 @@ def test_sync_repl_dynamic_plugin(topology, request): + 3. Should succeeds + 4. Should succeeds + """ +- ++ topology.standalone.enable_tls() + # Reset the instance in a default config + # Disable content sync plugin + topology.standalone.plugins.disable(name=PLUGIN_REPL_SYNC) +-- +2.52.0 + diff --git a/SOURCES/0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch b/SOURCES/0025-Issue-6778-Memory-leak-in-roles_cache_create_object_.patch deleted file mode 100644 index 286ed06..0000000 --- a/SOURCES/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/SOURCES/0025-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch b/SOURCES/0025-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch new file mode 100644 index 0000000..35619dd --- /dev/null +++ b/SOURCES/0025-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch @@ -0,0 +1,33 @@ +From bd57467f6efd8e398eb2e608331711bdd571d3ac Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Thu, 12 Feb 2026 09:58:54 -0500 +Subject: [PATCH] Issue 7248 - CLI - attribute uniqueness - fix usage for + exclude subtree option + +Description: + +Fix typo in usage message for the exclude subtree option + +relates: https://github.com/389ds/389-ds-base/issues/7248 + +Reviewed by: progier (Thanks!) +--- + src/lib389/lib389/cli_conf/plugins/attruniq.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py +index bc925eb1c..26ca5d819 100644 +--- a/src/lib389/lib389/cli_conf/plugins/attruniq.py ++++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py +@@ -127,7 +127,7 @@ def _add_parser_args(parser): + help='Sets the DN under which the plug-in checks for uniqueness of ' + 'the attributes value. This attribute is multi-valued (uniqueness-subtrees)') + parser.add_argument('--exclude-subtree', nargs='+', +- help='Sets subtrees that should not excludedfrom attribute uniqueness. ' ++ help='Sets subtrees that should be excluded from attribute uniqueness checks. ' + 'This attribute is multi-valued (uniqueness-exclude-subtrees)') + parser.add_argument('--across-all-subtrees', choices=['on', 'off'], type=str.lower, + help='If enabled (on), the plug-in checks that the attribute is unique across all subtrees ' +-- +2.52.0 + diff --git a/SOURCES/0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch b/SOURCES/0026-Issue-6850-AddressSanitizer-memory-leak-in-mdb_init.patch deleted file mode 100644 index b99f974..0000000 --- a/SOURCES/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/SOURCES/0026-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch b/SOURCES/0026-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch new file mode 100644 index 0000000..9e5ff53 --- /dev/null +++ b/SOURCES/0026-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch @@ -0,0 +1,201 @@ +From 180329d9ca672306f8e90d599890be3a6a8aa95c Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Thu, 12 Feb 2026 11:13:45 -0500 +Subject: [PATCH] Issue - CLI - dsctl db2index needs some hardening with MBD + +Description: + +The usage for dsctl db2index was confusing. The way the attr options and +backend name were displayed it looks like the backend name could come after +the attributes, but instead the backend name was treated as an attribute. + +Instead make the backend name required, and change the attribute naming to +require individual options instead of a list of values. + +Relates: https://github.com/389ds/389-ds-base/issues/7250 + +Reviewed by: progier(Thanks!) +--- + .../tests/suites/import/import_test.py | 2 +- + src/lib389/lib389/__init__.py | 40 ++++++------------- + src/lib389/lib389/cli_ctl/dbtasks.py | 37 +++++++---------- + 3 files changed, 27 insertions(+), 52 deletions(-) + +diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py +index 18caec633..058a4b3c7 100644 +--- a/dirsrvtests/tests/suites/import/import_test.py ++++ b/dirsrvtests/tests/suites/import/import_test.py +@@ -512,7 +512,7 @@ def test_entry_with_escaped_characters_fails_to_import_and_index(topo, _import_c + count += 1 + # Now re-index the database + topo.standalone.stop() +- topo.standalone.db2index() ++ topo.standalone.db2index(bename="userroot") + topo.standalone.start() + # Should not return error. + assert not topo.standalone.searchErrorsLog('error') +diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py +index 39a2852e5..ec47987be 100644 +--- a/src/lib389/lib389/__init__.py ++++ b/src/lib389/lib389/__init__.py +@@ -2985,7 +2985,7 @@ class DirSrv(SimpleLDAPObject, object): + + return True + +- def db2index(self, bename=None, suffixes=None, attrs=None, vlvTag=None): ++ def db2index(self, bename, suffixes=None, attrs=None, vlvTag=None): + """ + @param bename - The backend name to reindex + @param suffixes - List/tuple of suffixes to reindex, currently unused +@@ -2998,34 +2998,18 @@ class DirSrv(SimpleLDAPObject, object): + if self.status(): + self.log.error("db2index: Can not operate while directory server is running") + return False +- cmd = [prog, ] +- # No backend specified, do an upgrade on all backends +- # Backend and no attrs specified, reindex with all backend indexes +- # Backend and attr/s specified, reindex backend with attr/s +- if bename: +- cmd.append('db2index') +- cmd.append('-n') +- cmd.append(bename) +- if attrs: +- for attr in attrs: +- cmd.append('-t') +- cmd.append(attr) +- else: +- dse_ldif = DSEldif(self) +- indexes = dse_ldif.get_indexes(bename) +- if indexes: +- for idx in indexes: +- cmd.append('-t') +- cmd.append(idx) ++ cmd = [prog, 'db2index', '-n', bename, '-D', self.get_config_dir()] ++ if attrs: ++ for attr in attrs: ++ cmd.append('-t') ++ cmd.append(attr) + else: +- cmd.append('upgradedb') +- cmd.append('-a') +- now = datetime.now().isoformat() +- cmd.append(os.path.join(self.get_bak_dir(), 'reindex_%s' % now)) +- cmd.append('-f') +- +- cmd.append('-D') +- cmd.append(self.get_config_dir()) ++ dse_ldif = DSEldif(self) ++ indexes = dse_ldif.get_indexes(bename) ++ if indexes: ++ for idx in indexes: ++ cmd.append('-t') ++ cmd.append(idx) + + try: + result = subprocess.check_output(cmd, encoding='utf-8') +diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py +index 16da966d1..cd96cdaf7 100644 +--- a/src/lib389/lib389/cli_ctl/dbtasks.py ++++ b/src/lib389/lib389/cli_ctl/dbtasks.py +@@ -26,32 +26,18 @@ class IndexOrdering(Enum): + + + def dbtasks_db2index(inst, log, args): +- rtn = False +- if not args.backend: +- if not inst.db2index(): +- rtn = False +- else: +- rtn = True +- elif args.backend and not args.attr: +- if not inst.db2index(bename=args.backend): +- rtn = False +- else: +- rtn = True ++ inst.log = log ++ if not inst.db2index(bename=args.backend, attrs=args.attr): ++ log.fatal("db2index failed") ++ return False + else: +- if not inst.db2index(bename=args.backend, attrs=args.attr): +- rtn = False +- else: +- rtn = True +- if rtn: + log.info("db2index successful") +- return rtn +- else: +- log.fatal("db2index failed") +- return rtn ++ return True + + + def dbtasks_db2bak(inst, log, args): + # Needs an output name? ++ inst.log = log + if not inst.db2bak(args.archive): + log.fatal("db2bak failed") + return False +@@ -61,6 +47,7 @@ def dbtasks_db2bak(inst, log, args): + + def dbtasks_bak2db(inst, log, args): + # Needs the archive to restore. ++ inst.log = log + if not inst.bak2db(args.archive): + log.fatal("bak2db failed") + return False +@@ -70,6 +57,7 @@ def dbtasks_bak2db(inst, log, args): + + def dbtasks_db2ldif(inst, log, args): + # If export filename is provided, check if file path exists ++ inst.log = log + if args.ldif: + path = Path(args.ldif) + parent = path.parent.absolute() +@@ -88,6 +76,7 @@ def dbtasks_db2ldif(inst, log, args): + + def dbtasks_ldif2db(inst, log, args): + # Check if ldif file exists ++ inst.log = log + if not os.path.exists(args.ldif): + raise ValueError("The LDIF file does not exist: " + args.ldif) + +@@ -103,6 +92,7 @@ def dbtasks_ldif2db(inst, log, args): + + + def dbtasks_backups(inst, log, args): ++ inst.log = log + if args.delete: + # Delete backup + inst.del_backup(args.delete[0]) +@@ -117,6 +107,7 @@ def dbtasks_backups(inst, log, args): + + + def dbtasks_ldifs(inst, log, args): ++ inst.log = log + if args.delete: + # Delete LDIF file + inst.del_ldif(args.delete[0]) +@@ -131,6 +122,7 @@ def dbtasks_ldifs(inst, log, args): + + + def dbtasks_verify(inst, log, args): ++ inst.log = log + if not inst.dbverify(bename=args.backend): + log.fatal("dbverify failed") + return False +@@ -521,9 +513,8 @@ def dbtasks_index_check(inst, log, args): + + def create_parser(subcommands): + db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter) +- # db2index_parser.add_argument('suffix', help="The suffix to reindex. IE dc=example,dc=com.") +- db2index_parser.add_argument('backend', nargs="?", help="The backend to reindex. IE userRoot", default=False) +- db2index_parser.add_argument('--attr', nargs="*", help="The attribute's to reindex. IE --attr aci cn givenname", default=False) ++ db2index_parser.add_argument('backend', help="The backend to reindex. IE userRoot") ++ db2index_parser.add_argument('--attr', action='append', help="An attribute to reindex. IE: --attr member --attr cn ...") + db2index_parser.set_defaults(func=dbtasks_db2index) + + db2bak_parser = subcommands.add_parser('db2bak', help="Initialise a BDB backup of the database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter) +-- +2.52.0 + diff --git a/SOURCES/0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch b/SOURCES/0027-Issue-6848-AddressSanitizer-leak-in-do_search.patch deleted file mode 100644 index 6908ba1..0000000 --- a/SOURCES/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/SOURCES/0027-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch b/SOURCES/0027-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch new file mode 100644 index 0000000..0886182 --- /dev/null +++ b/SOURCES/0027-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch @@ -0,0 +1,41 @@ +From c831a7af2afb7cfd2eb958476a7a8d421ac5d339 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Fri, 13 Feb 2026 15:38:52 +0100 +Subject: [PATCH] Issue 7184 - (2nd) argparse.HelpFormatter + _format_actions_usage() is deprecated (#7257) + +Description: +`_format_actions_usage()` was also removed in Python 3.14.3. +Replace version check with `isinstance()` to handle the return type of +`_get_actions_usage_parts()` more robustly across Python versions. + +Relates: https://github.com/389ds/389-ds-base/issues/7184 +Fixes: https://github.com/389ds/389-ds-base/issues/7253 + +Reviewed by: @progier389 (Thanks!) +--- + src/lib389/lib389/cli_base/__init__.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py +index f1055aadc..3af8a46e6 100644 +--- a/src/lib389/lib389/cli_base/__init__.py ++++ b/src/lib389/lib389/cli_base/__init__.py +@@ -420,11 +420,11 @@ class CustomHelpFormatter(argparse.HelpFormatter): + else: + # Use _get_actions_usage_parts() for Python 3.13 and later + action_parts = self._get_actions_usage_parts(parent_arguments, []) +- if sys.version_info >= (3, 15): +- # Python 3.15 returns a tuple (list of actions, count of actions) ++ if isinstance(action_parts, tuple): ++ # Python 3.14.3+ and 3.15+ return a tuple (list of actions, count of actions) + formatted_options = ' '.join(action_parts[0]) + else: +- # Python 3.13 and 3.14 return a list of actions ++ # Earlier versions return a list of actions + formatted_options = ' '.join(action_parts) + + # If formatted_options already in usage - remove them +-- +2.52.0 + diff --git a/SOURCES/0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch b/SOURCES/0028-Issue-6865-AddressSanitizer-leak-in-agmt_update_init.patch deleted file mode 100644 index 99b5e6f..0000000 --- a/SOURCES/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/SOURCES/0028-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch b/SOURCES/0028-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch new file mode 100644 index 0000000..d016d69 --- /dev/null +++ b/SOURCES/0028-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch @@ -0,0 +1,32 @@ +From a7f8eb79dde03ac771de2335a2acb4aed48483a2 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Fri, 13 Feb 2026 16:27:25 +0100 +Subject: [PATCH] Issue 7213 - (2nd) MDB_BAD_VALSIZE error while handling VLV + (#7258) + +Decription: +Disable test_vlv_long_attribute_value on BDB as it hangs sometimes in +CI, blocking other pipelines. + +Relates: https://github.com/389ds/389-ds-base/issues/7213 + +Reviewed by: @progier389 (Thanks!) +--- + dirsrvtests/tests/suites/vlv/regression_test.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py +index 1cc03c303..94001af62 100644 +--- a/dirsrvtests/tests/suites/vlv/regression_test.py ++++ b/dirsrvtests/tests/suites/vlv/regression_test.py +@@ -1178,6 +1178,7 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr): + + + ++@pytest.mark.skipif(get_default_db_lib() == "bdb", reason="Hangs on BDB") + def test_vlv_long_attribute_value(topology_st, request): + """ + Test VLV with an entry containing a very long attribute value (2K). +-- +2.52.0 + diff --git a/SOURCES/0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch b/SOURCES/0029-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patch deleted file mode 100644 index e82a4f9..0000000 --- a/SOURCES/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/SOURCES/0029-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch b/SOURCES/0029-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch new file mode 100644 index 0000000..d57c972 --- /dev/null +++ b/SOURCES/0029-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch @@ -0,0 +1,34 @@ +From 75b1213dfb94495160a735c2688cee03d6be5a9a Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Fri, 13 Feb 2026 16:58:24 +0100 +Subject: [PATCH] Issue 7223 - Use lexicographical order for ancestorid (#7256) + +Description: +`ldbm_instance_create_default_indexes()` configured ancestorid with +integerOrderingMatch in the in-memory attrinfo, but ancestorid on disk +might be using lexicographic ordering (data before the upgrade or after +ldif2db import). + +Relates: https://github.com/389ds/389-ds-base/issues/7223 + +Reviewed by: @tbordaz (Thanks!) +--- + ldap/servers/slapd/back-ldbm/instance.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c +index 3fcdb5554..563cf96db 100644 +--- a/ldap/servers/slapd/back-ldbm/instance.c ++++ b/ldap/servers/slapd/back-ldbm/instance.c +@@ -239,7 +239,7 @@ ldbm_instance_create_default_indexes(backend *be) + * ancestorid is special, there is actually no such attr type + * but we still want to use the attr index file APIs. + */ +- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch"); ++ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0); + attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL); + slapi_entry_free(e); + } +-- +2.52.0 + diff --git a/SOURCES/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch b/SOURCES/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch deleted file mode 100644 index 154475e..0000000 --- a/SOURCES/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/SOURCES/0030-Issue-7066-7052-allow-password-history-to-be-set-to-.patch b/SOURCES/0030-Issue-7066-7052-allow-password-history-to-be-set-to-.patch new file mode 100644 index 0000000..ccab1a6 --- /dev/null +++ b/SOURCES/0030-Issue-7066-7052-allow-password-history-to-be-set-to-.patch @@ -0,0 +1,89 @@ +From 7809104b9edb2d05ceee7ca819152f365cc1beb0 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 11 Feb 2026 15:51:47 -0500 +Subject: [PATCH] Issue 7066/7052 - allow password history to be set to zero + and remove history + +Description: + +For local password policies the server was incorrectly rejecting updates that +set the value to zero. When password history is set to zero the old passwords +in the entry history are not cleaned as expected. + +relates: https://github.com/389ds/389-ds-base/issues/7052 +relates: https://github.com/389ds/389-ds-base/issues/7066 + +Reviewed by: progier(Thanks!) +--- + .../tests/suites/password/pwp_history_test.py | 9 ++++++--- + ldap/servers/slapd/modify.c | 2 +- + ldap/servers/slapd/pw.c | 13 ++++++++++++- + 3 files changed, 19 insertions(+), 5 deletions(-) + +diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py +index 8a15a1986..387824bc5 100644 +--- a/dirsrvtests/tests/suites/password/pwp_history_test.py ++++ b/dirsrvtests/tests/suites/password/pwp_history_test.py +@@ -108,8 +108,12 @@ def test_history_is_not_overwritten(topology_st, user): + user.set('userpassword', USER_PWD) + + +-def test_basic(topology_st, user): +- """Test basic password policy history feature functionality ++@pytest.mark.parametrize('policy', ++ [(pytest.param('global')), ++ (pytest.param('subtree')), ++ (pytest.param('user'))]) ++def test_basic(topology_st, user, policy): ++ """Test basic password policy history feature functionality with dynamic count reduction + + :id: 83d74f7d-3036-4944-8839-1b40bbf265ff + :setup: Standalone instance, a test user +@@ -238,7 +242,6 @@ def test_basic(topology_st, user): + + # + # Reset password by Directory Manager(admin reset) +- # + dm = DirectoryManager(topology_st.standalone) + dm.rebind() + time.sleep(.5) +diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c +index b0066faf8..d76427886 100644 +--- a/ldap/servers/slapd/modify.c ++++ b/ldap/servers/slapd/modify.c +@@ -87,7 +87,7 @@ static struct attr_value_check + {CONFIG_PW_WARNING_ATTRIBUTE, check_pw_duration_value, 0, -1}, + {CONFIG_PW_MINLENGTH_ATTRIBUTE, attr_check_minmax, 2, 512}, + {CONFIG_PW_MAXFAILURE_ATTRIBUTE, attr_check_minmax, 1, 32767}, +- {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 1, 24}, ++ {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 0, 24}, + {CONFIG_PW_LOCKDURATION_ATTRIBUTE, check_pw_duration_value, -1, -1}, + {CONFIG_PW_RESETFAILURECOUNT_ATTRIBUTE, check_pw_resetfailurecount_value, -1, -1}, + {CONFIG_PW_GRACELIMIT_ATTRIBUTE, attr_check_minmax, 0, -1}, +diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c +index cda1c404f..12cf759ce 100644 +--- a/ldap/servers/slapd/pw.c ++++ b/ldap/servers/slapd/pw.c +@@ -1532,7 +1532,18 @@ update_pw_history(Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_pw) + pwpolicy = new_passwdPolicy(pb, dn); + + if (pwpolicy->pw_inhistory == 0){ +- /* We are only enforcing the current password, just return */ ++ /* We are only enforcing the current password, just return but first ++ * cleanup any old passwords in the history */ ++ attribute.mod_type = "passwordHistory"; ++ attribute.mod_op = LDAP_MOD_REPLACE; ++ attribute.mod_values = NULL; ++ list_of_mods[0] = &attribute; ++ list_of_mods[1] = NULL; ++ mod_pb = slapi_pblock_new(); ++ slapi_modify_internal_set_pb_ext(mod_pb, sdn, list_of_mods, NULL, NULL, pw_get_componentID(), 0); ++ slapi_modify_internal_pb(mod_pb); ++ slapi_pblock_destroy(mod_pb); ++ + return res; + } + +-- +2.52.0 + diff --git a/SOURCES/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch b/SOURCES/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch deleted file mode 100644 index 4c84c24..0000000 --- a/SOURCES/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/SOURCES/0031-Issue-7243-UI-fix-certificate-table-and-modal.patch b/SOURCES/0031-Issue-7243-UI-fix-certificate-table-and-modal.patch new file mode 100644 index 0000000..8420bcd --- /dev/null +++ b/SOURCES/0031-Issue-7243-UI-fix-certificate-table-and-modal.patch @@ -0,0 +1,260 @@ +From 4209ff65310c54b6ab55594984201b1214fb75b2 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 16 Feb 2026 16:52:52 -0500 +Subject: [PATCH] Issue 7243 - UI - fix certificate table and modal + +Description: + +The certificate table was not handling sorting and pagination correctly, and +the add cert modal was not correctly selecting certs on disk + +This was fixed upstream for the hot cetificate features, but only backporting +the table/modal changes to older branches + +relates: https://github.com/389ds/389-ds-base/issues/7243 + +Reviewed by: spichugi(Thanks!) +--- + .../src/lib/security/securityModals.jsx | 4 +- + .../src/lib/security/securityTables.jsx | 85 ++++++------------- + 2 files changed, 29 insertions(+), 60 deletions(-) + +diff --git a/src/cockpit/389-console/src/lib/security/securityModals.jsx b/src/cockpit/389-console/src/lib/security/securityModals.jsx +index a42aa50d6..c4af753c0 100644 +--- a/src/cockpit/389-console/src/lib/security/securityModals.jsx ++++ b/src/cockpit/389-console/src/lib/security/securityModals.jsx +@@ -298,7 +298,9 @@ export class SecurityAddCertModal extends React.Component { + { ++ handleCertSelect(str); ++ }} + aria-label="FormSelect Input" + className="ds-cert-select" + validated={selectValidated} +diff --git a/src/cockpit/389-console/src/lib/security/securityTables.jsx b/src/cockpit/389-console/src/lib/security/securityTables.jsx +index fce4cb04e..a6c64e6c1 100644 +--- a/src/cockpit/389-console/src/lib/security/securityTables.jsx ++++ b/src/cockpit/389-console/src/lib/security/securityTables.jsx +@@ -4,7 +4,6 @@ import { + Grid, + GridItem, + Pagination, +- PaginationVariant, + SearchInput, + Tooltip, + } from '@patternfly/react-core'; +@@ -94,10 +93,10 @@ class KeyTable extends React.Component { + ]; + + handleSort(_event, index, direction) { +- const sortedRows = [...this.state.rows].sort((a, b) => ++ const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); +- ++ + this.setState({ + sortBy: { + index, +@@ -127,7 +126,7 @@ class KeyTable extends React.Component { + > + {_("What is an orphan key?")} + +- + + {columns.map((column, idx) => ( +-
+- + +@@ -224,7 +223,7 @@ class CSRTable extends React.Component { + } + + handleSort(_event, index, direction) { +- const sortedRows = [...this.state.rows].sort((a, b) => ++ const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ +@@ -316,7 +315,7 @@ class CSRTable extends React.Component { + onClear={(evt) => this.handleSearchChange(evt, '')} + /> + } +- + + {columns.map((column, idx) => ( +-
+- + +@@ -418,41 +417,11 @@ class CertTable extends React.Component { + } + + handleSort(_event, columnIndex, direction) { +- const sorted_rows = []; +- const rows = []; +- let count = 0; +- +- // Convert the rows pairings into a sortable array +- for (let idx = 0; idx < this.state.rows.length; idx += 2) { +- sorted_rows.push({ +- expandedRow: this.state.rows[idx + 1], +- 1: this.state.rows[idx].cells[0].content, +- 2: this.state.rows[idx].cells[1].content, +- 3: this.state.rows[idx].cells[2].content, +- issuer: this.state.rows[idx].issuer, +- flags: this.state.rows[idx].flags +- }); +- } ++ const rows = [...this.state.rows]; + +- sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1); ++ rows.sort((a, b) => (a.cells[columnIndex].content > b.cells[columnIndex].content) ? 1 : -1); + if (direction !== SortByDirection.asc) { +- sorted_rows.reverse(); +- } +- +- for (const srow of sorted_rows) { +- rows.push({ +- isOpen: false, +- cells: [ +- { content: srow[1] }, +- { content: srow[2] }, +- { content: srow[3] } +- ], +- issuer: srow.issuer, +- flags: srow.flags, +- }); +- srow.expandedRow.parent = count; +- rows.push(srow.expandedRow); +- count += 2; ++ rows.reverse(); + } + + this.setState({ +@@ -534,18 +503,16 @@ class CertTable extends React.Component { + rows.push( + { + isOpen: false, +- cells: [cert.attrs.nickname, cert.attrs.subject, cert.attrs.expires], ++ cells: [ ++ { content: cert.attrs.nickname }, ++ { content: cert.attrs.subject }, ++ { content: cert.attrs.expires } ++ ], + issuer: cert.attrs.issuer, + flags: cert.attrs.flags, +- +- }, +- { +- parent: count, +- fullWidth: true, +- cells: [{ title: this.getExpandedRow(cert.attrs.issuer, cert.attrs.flags) }] +- }, ++ } + ); +- count += 2; ++ count += 1; + } + + this.setState({ +@@ -587,7 +554,7 @@ class CertTable extends React.Component { + onChange={this.handleSearchChange} + onClear={(evt) => this.handleSearchChange(evt, '')} + />} +- +@@ -626,7 +593,7 @@ class CertTable extends React.Component { + + ))} + +@@ -646,7 +613,7 @@ class CertTable extends React.Component { +
+- +
+ {hasRows && + this.handleSearchChange(evt, '')} + /> +- + + + {this.state.columns.map((column, idx) => ( +-
+- + +-- +2.52.0 + diff --git a/SOURCES/0032-Issue-6910-Fix-latest-coverity-issues.patch b/SOURCES/0032-Issue-6910-Fix-latest-coverity-issues.patch deleted file mode 100644 index 7f9f8d5..0000000 --- a/SOURCES/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/SOURCES/0032-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch b/SOURCES/0032-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch new file mode 100644 index 0000000..39bc237 --- /dev/null +++ b/SOURCES/0032-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch @@ -0,0 +1,538 @@ +From 09b6f770f15ee8d333377b7902e18b78f4f7008c Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Wed, 18 Feb 2026 09:26:57 +0100 +Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for + parentid (#7264) + +Description: +integerOrderingMatch was introduced as a requirement for parentid and +ancestorid indexes for performance reasons. But after #7096 the order +for parentid doesn't make a lot of difference. + +Fix Description: +* Remove integerOrderingMatch requirement for parentid. +* Read only first 100 keys from dbscan in index ordering check +* Do not run dsctl index-check during RPM upgrade + +Relates: https://github.com/389ds/389-ds-base/pull/7223 + +Reviewed by: @progier389, @tbordaz (Thanks!) +--- + .../healthcheck/health_system_indexes_test.py | 83 ++++---------- + ldap/servers/slapd/upgrade.c | 105 ------------------ + rpm/389-ds-base.spec.in | 3 - + src/lib389/lib389/backend.py | 5 +- + src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++--------- + 5 files changed, 73 insertions(+), 222 deletions(-) + +diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +index 3b3651b38..71a9f590a 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py +@@ -180,7 +180,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled): + + + def test_missing_matching_rule(topology_st, log_buffering_enabled): +- """Check if healthcheck returns DSBLE0007 code when parentId index is missing integerOrderingMatch ++ """Check that healthcheck does NOT report DSBLE0007 when parentId index is missing integerOrderingMatch. ++ Both lexicographic and integer orderings are valid for parentid. + + :id: 7ffa71db-8995-430a-bed8-59bce944221c + :setup: Standalone instance +@@ -190,19 +191,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled): + 3. Use healthcheck without --json option + 4. Use healthcheck with --json option + 5. Re-add the matching rule +- 6. Use healthcheck without --json option +- 7. Use healthcheck with --json option + :expectedresults: + 1. Success + 2. Success +- 3. healthcheck reports DSBLE0007 code and related details +- 4. healthcheck reports DSBLE0007 code and related details ++ 3. healthcheck reports no issues found ++ 4. healthcheck reports no issues found + 5. Success +- 6. healthcheck reports no issues found +- 7. healthcheck reports no issues found + """ + +- RET_CODE = "DSBLE0007" + PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" + + standalone = topology_st.standalone +@@ -211,17 +207,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled): + parentid_index = Index(standalone, PARENTID_DN) + parentid_index.remove("nsMatchingRule", "integerOrderingMatch") + +- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE) +- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE) ++ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) ++ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) + + log.info("Re-add the integerOrderingMatch matching rule") + parentid_index = Index(standalone, PARENTID_DN) + parentid_index.add("nsMatchingRule", "integerOrderingMatch") + standalone.restart() + +- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT) +- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT) +- + + def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled): + """Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing +@@ -911,7 +904,9 @@ def test_index_check_fixes_ancestorid_config(topology_st): + + + def test_index_check_fixes_missing_matching_rule(topology_st): +- """Check if dsctl index-check --fix adds missing integerOrderingMatch ++ """Check that removing integerOrderingMatch from parentid config is not ++ flagged as an issue when disk ordering cannot be determined. ++ Both lexicographic and integer orderings are valid for parentid. + + :id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f + :setup: Standalone instance +@@ -919,18 +914,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st): + 1. Create DS instance + 2. Stop the server + 3. Remove integerOrderingMatch from parentid index using DSEldif +- 4. Run dsctl index-check (should detect issue) +- 5. Run dsctl index-check --fix +- 6. Verify integerOrderingMatch was added back +- 7. Start the server ++ 4. Run dsctl index-check (should NOT detect issue since disk ordering is unknown) ++ 5. Start the server + :expectedresults: + 1. Success + 2. Success + 3. Success +- 4. index-check returns False and detects missing matching rule +- 5. index-check returns True after fix +- 6. integerOrderingMatch is present +- 7. Success ++ 4. index-check returns True (no issues, disk ordering unknown) ++ 5. Success + """ + from lib389.cli_ctl.dbtasks import dbtasks_index_check + from lib389.dseldif import DSEldif +@@ -964,34 +955,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st): + f"integerOrderingMatch should be removed, but found: {mr}" + log.info("integerOrderingMatch removed from parentid index") + +- log.info("Run index-check without --fix (should detect issue)") ++ log.info("Run index-check (should NOT detect issue - disk ordering unknown)") + args = FakeArgs() + args.backend = "userRoot" + args.fix = False + + result = dbtasks_index_check(standalone, topology_st.logcap.log, args) +- assert result is False, "index-check should detect missing matching rule" +- assert topology_st.logcap.contains("missing integerOrderingMatch") ++ assert result is True, \ ++ "index-check should not flag missing integerOrderingMatch when disk ordering is unknown" ++ assert topology_st.logcap.contains("could not determine disk ordering") + topology_st.logcap.flush() + +- log.info("Run index-check with --fix") +- args.fix = True +- result = dbtasks_index_check(standalone, topology_st.logcap.log, args) +- assert result is True, "index-check --fix should succeed" +- assert topology_st.logcap.contains("integerOrderingMatch") +- topology_st.logcap.flush() +- +- log.info("Verify integerOrderingMatch was added back") +- dse_ldif = DSEldif(standalone) # Reload to get fresh data +- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") +- assert matching_rules is not None, "nsMatchingRule should be present" +- found_int_order = False +- for mr in matching_rules: +- if "integerorderingmatch" in mr.lower(): +- found_int_order = True +- break +- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}" +- log.info("integerOrderingMatch successfully added back") ++ log.info("Restore integerOrderingMatch and start the server") ++ dse_ldif = DSEldif(standalone) ++ dse_ldif.add(parentid_dn, "nsMatchingRule", "integerOrderingMatch") + + log.info("Start the server") + standalone.start() +@@ -1081,7 +1058,7 @@ def test_index_check_fixes_multiple_issues(topology_st): + :steps: + 1. Create DS instance + 2. Stop the server +- 3. Add multiple issues: scanlimit, ancestorid config, missing matching rule ++ 3. Add multiple issues: scanlimit and ancestorid config + 4. Run dsctl index-check (should detect all issues) + 5. Run dsctl index-check --fix + 6. Verify all issues were fixed +@@ -1123,14 +1100,6 @@ def test_index_check_fixes_multiple_issues(topology_st): + ] + dse_ldif.add_entry(ancestorid_entry) + +- log.info("Add issue 3: Remove integerOrderingMatch from parentid") +- dse_ldif = DSEldif(standalone) # Reload +- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") +- if matching_rules: +- for mr in matching_rules: +- if "integerorderingmatch" in mr.lower(): +- dse_ldif.delete(parentid_dn, "nsMatchingRule", mr) +- + log.info("Run index-check without --fix (should detect all issues)") + args = FakeArgs() + args.backend = "userRoot" +@@ -1161,16 +1130,6 @@ def test_index_check_fixes_multiple_issues(topology_st): + cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True) + assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}" + +- # Check matching rule added back +- matching_rules = dse_ldif.get(parentid_dn, "nsMatchingRule") +- found_int_order = False +- if matching_rules: +- for mr in matching_rules: +- if "integerorderingmatch" in mr.lower(): +- found_int_order = True +- break +- assert found_int_order, f"integerOrderingMatch should be present, got: {matching_rules}" +- + log.info("All issues verified as fixed") + + log.info("Run index-check again to confirm all clear") +diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c +index d9156cae9..537c38feb 100644 +--- a/ldap/servers/slapd/upgrade.c ++++ b/ldap/servers/slapd/upgrade.c +@@ -500,107 +500,6 @@ upgrade_remove_ancestorid_index_config(void) + return uresult; + } + +-/* +- * Check if parentid/ancestorid indexes are missing the integerOrderingMatch +- * matching rule. +- * +- * This function logs a warning if we detect this condition, advising +- * the administrator to reindex the affected attributes. +- */ +-static upgrade_status +-upgrade_check_id_index_matching_rule(void) +-{ +- struct slapi_pblock *pb = slapi_pblock_new(); +- Slapi_Entry **backends = NULL; +- const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config"; +- const char *be_filter = "(objectclass=nsBackendInstance)"; +- const char *attrs_to_check[] = {"parentid", NULL}; +- upgrade_status uresult = UPGRADE_SUCCESS; +- +- /* Search for all backend instances */ +- slapi_search_internal_set_pb( +- pb, be_base_dn, +- LDAP_SCOPE_ONELEVEL, +- be_filter, NULL, 0, NULL, NULL, +- plugin_get_default_component_id(), 0); +- slapi_search_internal_pb(pb); +- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends); +- +- if (backends) { +- for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) { +- const char *be_dn = slapi_entry_get_dn_const(backends[be_idx]); +- const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn"); +- if (!be_dn || !be_name) { +- continue; +- } +- +- /* Check each attribute that should have integerOrderingMatch */ +- for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) { +- const char *attr_name = attrs_to_check[attr_idx]; +- struct slapi_pblock *idx_pb = slapi_pblock_new(); +- Slapi_Entry **idx_entries = NULL; +- char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,%s", +- attr_name, be_dn); +- char *idx_filter = "(objectclass=nsIndex)"; +- PRBool has_matching_rule = PR_FALSE; +- +- if (!idx_dn) { +- slapi_pblock_destroy(idx_pb); +- continue; +- } +- +- slapi_search_internal_set_pb( +- idx_pb, idx_dn, +- LDAP_SCOPE_BASE, +- idx_filter, NULL, 0, NULL, NULL, +- plugin_get_default_component_id(), 0); +- slapi_search_internal_pb(idx_pb); +- slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries); +- +- if (idx_entries && idx_entries[0]) { +- /* Index exists, check if it has integerOrderingMatch */ +- Slapi_Attr *mr_attr = NULL; +- if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) { +- Slapi_Value *sval = NULL; +- int idx; +- for (idx = slapi_attr_first_value(mr_attr, &sval); +- idx != -1; +- idx = slapi_attr_next_value(mr_attr, idx, &sval)) { +- const struct berval *bval = slapi_value_get_berval(sval); +- if (bval && bval->bv_val && +- strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) { +- has_matching_rule = PR_TRUE; +- break; +- } +- } +- } +- +- if (!has_matching_rule) { +- /* Index exists but doesn't have integerOrderingMatch, log a warning */ +- slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule", +- "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. " +- "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. " +- "To fix this, add the matching rule and reindex: " +- "dsconf 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) +@@ -637,10 +536,6 @@ upgrade_server(void) + return UPGRADE_FAILURE; + } + +- if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) { +- return UPGRADE_FAILURE; +- } +- + return UPGRADE_SUCCESS; + } + +diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in +index 0175dfa7c..f78258f6a 100644 +--- a/rpm/389-ds-base.spec.in ++++ b/rpm/389-ds-base.spec.in +@@ -667,9 +667,6 @@ for dir in "$instbase"/slapd-* ; do + else + echo "instance $inst is not running" >> "$output" 2>&1 || : + fi +- # Run index-check on all instances (running or not) +- # This fixes index ordering mismatches from older versions +- dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || : + ninst=$((ninst + 1)) + done + +diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py +index 1d9be4683..29a1796ac 100644 +--- a/src/lib389/lib389/backend.py ++++ b/src/lib389/lib389/backend.py +@@ -617,9 +617,10 @@ class Backend(DSLdapObject): + # Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c + # Note: entryrdn and ancestorid are internal system indexes that are not + # exposed in cn=config - they are managed internally by the server. +- # Only parentid has a DSE config entry (for the integerOrderingMatch rule). ++ # parentid works correctly with both lexicographic and integer ordering, ++ # so integerOrderingMatch is not required. + expected_system_indexes = { +- 'parentid': {'types': ['eq'], 'matching_rule': 'integerOrderingMatch'}, ++ 'parentid': {'types': ['eq'], 'matching_rule': None}, + 'objectClass': {'types': ['eq'], 'matching_rule': None}, + 'aci': {'types': ['pres'], 'matching_rule': None}, + 'nscpEntryDN': {'types': ['eq'], 'matching_rule': None}, +diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py +index cd96cdaf7..b02de203f 100644 +--- a/src/lib389/lib389/cli_ctl/dbtasks.py ++++ b/src/lib389/lib389/cli_ctl/dbtasks.py +@@ -10,6 +10,7 @@ + import glob + import os + import re ++import signal + import subprocess + from enum import Enum + from lib389._constants import TaskWarning +@@ -263,45 +264,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log): + if not index_file: + return IndexOrdering.UNKNOWN + ++ # Only read the first 100 lines from dbscan to avoid scanning the ++ # entire index (which can take hours on large databases). + try: +- result = subprocess.run( ++ proc = subprocess.Popen( + [dbscan_path, "-f", index_file], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, +- timeout=60, + ) + +- if result.returncode != 0: +- log.warning(" dbscan returned non-zero exit code for %s", index_file) +- return IndexOrdering.UNKNOWN +- +- # Parse keys from dbscan output + keys = [] +- for line in result.stdout.split("\n"): ++ line_count = 0 ++ assert proc.stdout is not None ++ for line in proc.stdout: ++ line_count += 1 ++ if line_count > 100: ++ break + line = line.strip() + if line.startswith("="): + match = re.match(r"^=(\d+)", line) + if match: + keys.append(int(match.group(1))) + ++ proc.terminate() ++ try: ++ proc.wait(timeout=5) ++ except subprocess.TimeoutExpired: ++ proc.kill() ++ proc.wait() ++ ++ if proc.returncode not in (0, -signal.SIGTERM): ++ log.warning(" dbscan returned non-zero exit code for %s", index_file) ++ return IndexOrdering.UNKNOWN ++ + if len(keys) < 2: + return IndexOrdering.UNKNOWN + + # Check if keys are in integer order by looking for decreasing numeric values + # (which would indicate lexicographic ordering, e.g., "3" < "30" < "4") + prev_id = keys[0] +- for i in range(1, min(len(keys), 100)): +- current_id = keys[i] ++ for current_id in keys[1:]: + if prev_id > current_id: + return IndexOrdering.LEXICOGRAPHIC + prev_id = current_id + + return IndexOrdering.INTEGER + +- except subprocess.TimeoutExpired: +- log.warning(" dbscan timed out for %s", index_file) +- return IndexOrdering.UNKNOWN + except OSError as e: + log.warning(" Error running dbscan: %s", e) + return IndexOrdering.UNKNOWN +@@ -375,8 +384,7 @@ def dbtasks_index_check(inst, log, args): + + # Track all issues found + all_ok = True +- mismatches = [] # (backend, index_name) tuples needing reindex +- missing_matching_rules = [] # (backend, index_name) tuples missing integerOrderingMatch ++ config_fixes = [] # (backend, index_name, action) tuples: action is "add_mr" or "remove_mr" + scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit + ancestorid_configs_to_remove = [] # backend names with ancestorid config entries + remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes +@@ -409,13 +417,6 @@ def dbtasks_index_check(inst, log, args): + + if disk_ordering == IndexOrdering.UNKNOWN: + log.info(" %s - could not determine disk ordering, skipping", index_name) +- # For parentid, still check if matching rule is missing +- if index_name == "parentid": +- config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name) +- if not config_has_int_order: +- log.warning(" %s - missing integerOrderingMatch in config", index_name) +- missing_matching_rules.append((backend, index_name)) +- all_ok = False + continue + + config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name) +@@ -423,18 +424,15 @@ def dbtasks_index_check(inst, log, args): + log.info(" %s - config: %s, disk: %s", + index_name, config_desc, disk_ordering.value) + +- # For parentid, the desired state is always integer ordering ++ # Both orderings are valid for parentid, but config must match disk. + if index_name == "parentid": +- if not config_has_int_order: +- log.warning(" %s - missing integerOrderingMatch in config", index_name) +- if (backend, index_name) not in missing_matching_rules: +- missing_matching_rules.append((backend, index_name)) ++ if config_has_int_order and disk_ordering == IndexOrdering.LEXICOGRAPHIC: ++ log.warning(" %s - MISMATCH: config has integerOrderingMatch but disk is lexicographic", index_name) ++ config_fixes.append((backend, index_name, "remove_mr")) + all_ok = False +- +- if disk_ordering == IndexOrdering.LEXICOGRAPHIC: +- log.warning(" %s - disk ordering is lexicographic, needs reindex", index_name) +- if (backend, index_name) not in mismatches: +- mismatches.append((backend, index_name)) ++ elif not config_has_int_order and disk_ordering == IndexOrdering.INTEGER: ++ log.warning(" %s - MISMATCH: config is lexicographic but disk has integer ordering", index_name) ++ config_fixes.append((backend, index_name, "add_mr")) + all_ok = False + + # Handle issues +@@ -480,26 +478,27 @@ def dbtasks_index_check(inst, log, args): + log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e) + return False + +- # Add missing matching rules to dse.ldif +- for backend, index_name in missing_matching_rules: ++ # Fix config-vs-disk ordering mismatches by adjusting config to match disk ++ for backend, index_name, action in config_fixes: + index_dn = "cn={},cn=index,cn={},cn=ldbm database,cn=plugins,cn=config".format( + index_name, backend + ) +- log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend) +- try: +- dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch") +- log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name) +- except Exception as e: +- log.error(" Failed to update dse.ldif for %s: %s", index_name, e) +- return False +- +- # Reindex indexes with disk ordering issues +- for backend, index_name in mismatches: +- log.info(" Reindexing %s in backend %s...", index_name, backend) +- if not inst.db2index(bename=backend, attrs=[index_name]): +- log.error(" Failed to reindex %s", index_name) +- return False +- log.info(" Reindex of %s completed successfully", index_name) ++ if action == "add_mr": ++ log.info(" Adding integerOrderingMatch to %s in backend %s...", index_name, backend) ++ try: ++ dse_ldif.add(index_dn, "nsMatchingRule", "integerOrderingMatch") ++ log.info(" Updated dse.ldif with integerOrderingMatch for %s", index_name) ++ except Exception as e: ++ log.error(" Failed to update dse.ldif for %s: %s", index_name, e) ++ return False ++ elif action == "remove_mr": ++ log.info(" Removing integerOrderingMatch from %s in backend %s...", index_name, backend) ++ try: ++ dse_ldif.delete(index_dn, "nsMatchingRule", "integerOrderingMatch") ++ log.info(" Removed integerOrderingMatch from %s", index_name) ++ except Exception as e: ++ log.error(" Failed to remove integerOrderingMatch from %s: %s", index_name, e) ++ return False + + log.info("All issues fixed") + return True +@@ -563,5 +562,5 @@ def create_parser(subcommands): + index_check_parser.add_argument('backend', nargs='?', default=None, + help="Backend to check. If not specified, all backends are checked.") + index_check_parser.add_argument('--fix', action='store_true', default=False, +- help="Fix mismatches by reindexing affected indexes") ++ help="Fix mismatches by adjusting config to match on-disk data") + index_check_parser.set_defaults(func=dbtasks_index_check) +-- +2.52.0 + diff --git a/SOURCES/0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch b/SOURCES/0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch deleted file mode 100644 index f3dbf20..0000000 --- a/SOURCES/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/SOURCES/0033-Issue-7053-Remove-memberof_del_dn_from_groups-from-M.patch b/SOURCES/0033-Issue-7053-Remove-memberof_del_dn_from_groups-from-M.patch new file mode 100644 index 0000000..adbc1fb --- /dev/null +++ b/SOURCES/0033-Issue-7053-Remove-memberof_del_dn_from_groups-from-M.patch @@ -0,0 +1,202 @@ +From 31bef3852f7064c73f66974d7c41262d2a724eab Mon Sep 17 00:00:00 2001 +From: Alex Kulberg +Date: Tue, 9 Dec 2025 17:11:56 +0300 +Subject: [PATCH] Issue 7053 - Remove memberof_del_dn_from_groups from MemberOf + plugin (#7064) + +Bug Description: + +The member plugin creates redundant changes to the member attribute +in groups when deleting a user, although the referential integrity +of the member attribute should be controlled by the Referential Integrity plugin. +Furthermore, memberof doesn't take replication of operations into account +and performs the change on every server instance in the topology. + +Fix Description: + +Remove the `memberof_del_dn_from_groups` function from the MemberOf plugin, +completely transferring responsibility for deleting users from groups +to the Referential Integrity plugin. + +Relates: https://github.com/389ds/389-ds-base/issues/7053 + +Reviewed by: @tbordaz +--- + .../memberof_include_scopes_test.py | 13 ++- + ldap/servers/plugins/memberof/memberof.c | 79 ------------------- + 2 files changed, 9 insertions(+), 83 deletions(-) + +diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py +index e1d3b0a96..347eb880f 100644 +--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py ++++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py +@@ -13,7 +13,7 @@ import time + from lib389.utils import ensure_str + from lib389.topologies import topology_st as topo + from lib389._constants import * +-from lib389.plugins import MemberOfPlugin ++from lib389.plugins import MemberOfPlugin, ReferentialIntegrityPlugin + from lib389.idm.user import UserAccount, UserAccounts + from lib389.idm.group import Group, Groups + from lib389.idm.nscontainer import nsContainers +@@ -77,6 +77,13 @@ def test_multiple_scopes(topo): + + inst = topo.standalone + ++ EXCLUDED_SUBTREE = 'cn=exclude,%s' % SUFFIX ++ # enable Referential Integrity plugin ++ # to correctly process the 'member' attribute ++ refint = ReferentialIntegrityPlugin(inst) ++ refint.add_excludescope(EXCLUDED_SUBTREE) ++ refint.enable() ++ + # configure plugin + memberof = MemberOfPlugin(inst) + memberof.enable() +@@ -106,7 +113,6 @@ def test_multiple_scopes(topo): + check_membership(inst, f'uid=test_m3,{SUBTREE_3}', f'cn=g3,{SUBTREE_3}', False) + + # Set exclude scope +- EXCLUDED_SUBTREE = 'cn=exclude,%s' % SUFFIX + EXCLUDED_USER = f"uid=test_m1,{EXCLUDED_SUBTREE}" + INCLUDED_USER = f"uid=test_m1,{SUBTREE_1}" + GROUP_DN = f'cn=g1,{SUBTREE_1}' +@@ -122,9 +128,8 @@ def test_multiple_scopes(topo): + # Check memberOf and group are cleaned up + check_membership(inst, EXCLUDED_USER, GROUP_DN, False) + group = Group(topo.standalone, dn=GROUP_DN) +- assert not group.present("member", EXCLUDED_USER) + assert not group.present("member", INCLUDED_USER) +- ++ assert not group.present("member", EXCLUDED_USER) + + if __name__ == '__main__': + # Run isolated +diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c +index c4b5afee7..5de2e9e72 100644 +--- a/ldap/servers/plugins/memberof/memberof.c ++++ b/ldap/servers/plugins/memberof/memberof.c +@@ -151,7 +151,6 @@ static void memberof_set_plugin_id(void *plugin_id); + static int memberof_compare(MemberOfConfig *config, const void *a, const void *b); + static int memberof_qsort_compare(const void *a, const void *b); + static void memberof_load_array(Slapi_Value **array, Slapi_Attr *attr); +-static int memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_Entry *e, Slapi_DN *sdn); + static int memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_DN *sdn, MemberOfConfig *config, char **types, plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache); + static int memberof_is_direct_member(MemberOfConfig *config, Slapi_Value *groupdn, Slapi_Value *memberdn); + static int memberof_is_grouping_attr(char *type, MemberOfConfig *config); +@@ -540,21 +539,6 @@ deferred_modrdn_func(MemberofDeferredModrdnTask *task) + * attributes to refer to the new name. */ + if (ret == LDAP_SUCCESS && pre_sdn && post_sdn) { + if (!memberof_entry_in_scope(&configCopy, &post_entry_info)) { +- /* +- * After modrdn the group contains both the pre and post DN's as +- * members, so we need to cleanup both in this case. +- */ +- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, pre_e, pre_sdn))) { +- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, +- "deferred_modrdn_func - Delete dn failed for preop entry(%s), error (%d)\n", +- slapi_sdn_get_dn(pre_sdn), ret); +- } +- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, post_e, post_sdn))) { +- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, +- "deferred_modrdn_func - Delete dn failed for postop entry(%s), error (%d)\n", +- slapi_sdn_get_dn(post_sdn), ret); +- } +- + if (ret == LDAP_SUCCESS && pre_e && configCopy.group_filter && + 0 == slapi_filter_test_simple(pre_e, configCopy.group_filter)) + { +@@ -638,16 +622,6 @@ deferred_del_func(MemberofDeferredDelTask *task) + free_configCopy = PR_TRUE; + memberof_unlock_config(); + +- /* remove this DN from the +- * membership lists of groups +- */ +- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, e, sdn))) { +- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, +- "deferred_del_func - Error deleting dn (%s) from group. Error (%d)\n", +- slapi_sdn_get_dn(sdn), ret); +- goto bail; +- } +- + /* is the entry of interest as a group? */ + if (e && configCopy.group_filter && 0 == slapi_filter_test_simple(e, configCopy.group_filter)) { + Slapi_Attr *attr = 0; +@@ -1456,16 +1430,6 @@ memberof_postop_del(Slapi_PBlock *pb) + memberof_copy_config(&configCopy, memberof_get_config()); + memberof_unlock_config(); + +- /* remove this DN from the +- * membership lists of groups +- */ +- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, e, sdn))) { +- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, +- "memberof_postop_del - Error deleting dn (%s) from group. Error (%d)\n", +- slapi_sdn_get_dn(sdn), ret); +- goto bail; +- } +- + /* is the entry of interest as a group? */ + if (e && configCopy.group_filter && 0 == slapi_filter_test_simple(e, configCopy.group_filter)) { + Slapi_Attr *attr = 0; +@@ -1496,34 +1460,6 @@ done: + } + + +-/* Deletes a member dn from all groups that refer to it. */ +-static int +-memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_Entry *e, Slapi_DN *sdn) +-{ +- char *groupattrs[2] = {0, 0}; +- int rc = LDAP_SUCCESS; +- int cached = 0; +- +- /* Loop through each grouping attribute to find groups that have +- * dn as a member. For any matches, delete the dn value from the +- * same grouping attribute. */ +- for (size_t i = 0; config->groupattrs && config->groupattrs[i] && rc == LDAP_SUCCESS; i++) { +- memberof_del_dn_data data = {(char *)slapi_sdn_get_dn(sdn), +- config->groupattrs[i], config}; +- +- groupattrs[0] = config->groupattrs[i]; +- +- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, +- "memberof_del_dn_from_groups: Ancestors of %s attr: %s\n", +- slapi_sdn_get_dn(sdn), +- groupattrs[0]); +- rc = memberof_call_foreach_dn(pb, e, sdn, config, groupattrs, +- memberof_del_dn_type_callback, &data, &cached, PR_FALSE); +- } +- +- return rc; +-} +- + int + memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data) + { +@@ -1904,21 +1840,6 @@ memberof_postop_modrdn(Slapi_PBlock *pb) + * attributes to refer to the new name. */ + if (ret == LDAP_SUCCESS && pre_sdn && post_sdn) { + if (!memberof_entry_in_scope(&configCopy, &post_entry_info)) { +- /* +- * After modrdn the group contains both the pre and post DN's as +- * members, so we need to cleanup both in this case. +- */ +- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, pre_e, pre_sdn))) { +- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, +- "memberof_postop_modrdn - Delete dn failed for preop entry(%s), error (%d)\n", +- slapi_sdn_get_dn(pre_sdn), ret); +- } +- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, post_e, post_sdn))) { +- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, +- "memberof_postop_modrdn - Delete dn failed for postop entry(%s), error (%d)\n", +- slapi_sdn_get_dn(post_sdn), ret); +- } +- + if (ret == LDAP_SUCCESS && pre_e && configCopy.group_filter && + 0 == slapi_filter_test_simple(pre_e, configCopy.group_filter)) { + /* is the entry of interest as a group? */ +-- +2.52.0 + diff --git a/SOURCES/0034-Issue-5853-Update-concread-to-0.5.10.patch b/SOURCES/0034-Issue-5853-Update-concread-to-0.5.10.patch new file mode 100644 index 0000000..038ba0f --- /dev/null +++ b/SOURCES/0034-Issue-5853-Update-concread-to-0.5.10.patch @@ -0,0 +1,952 @@ +From 0c5f7b9909ababc53e73e9469da6e30b81637cd0 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Mon, 23 Feb 2026 09:49:52 +0100 +Subject: [PATCH] Issue 5853 - Update concread to 0.5.10 + +Description: +Update concread to 0.5.10 and update Cargo.lock + +Relates: https://github.com/389ds/389-ds-base/issues/5853 + +Reviewed by: @droideck (Thanks!) +--- + src/Cargo.lock | 515 +++++++++++++++++++++++---------------- + src/librslapd/Cargo.toml | 2 +- + 2 files changed, 312 insertions(+), 205 deletions(-) + +diff --git a/src/Cargo.lock b/src/Cargo.lock +index 87aeee852..425371478 100644 +--- a/src/Cargo.lock ++++ b/src/Cargo.lock +@@ -2,27 +2,18 @@ + # It is not intended for manual editing. + version = 3 + +-[[package]] +-name = "addr2line" +-version = "0.24.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +-dependencies = [ +- "gimli", +-] +- +-[[package]] +-name = "adler2" +-version = "2.0.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +- + [[package]] + name = "allocator-api2" + version = "0.2.21" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + ++[[package]] ++name = "anyhow" ++version = "1.0.102" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" ++ + [[package]] + name = "atty" + version = "0.2.14" +@@ -40,21 +31,6 @@ version = "1.5.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +-[[package]] +-name = "backtrace" +-version = "0.3.75" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +-dependencies = [ +- "addr2line", +- "cfg-if", +- "libc", +- "miniz_oxide", +- "object", +- "rustc-demangle", +- "windows-targets", +-] +- + [[package]] + name = "base64" + version = "0.13.1" +@@ -69,9 +45,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + + [[package]] + name = "bitflags" +-version = "2.9.1" ++version = "2.11.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" ++checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + + [[package]] + name = "byteorder" +@@ -86,8 +62,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" + dependencies = [ + "clap", +- "heck", +- "indexmap", ++ "heck 0.4.1", ++ "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", +@@ -100,10 +76,11 @@ dependencies = [ + + [[package]] + name = "cc" +-version = "1.2.27" ++version = "1.2.56" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" ++checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" + dependencies = [ ++ "find-msvc-tools", + "jobserver", + "libc", + "shlex", +@@ -111,9 +88,9 @@ dependencies = [ + + [[package]] + name = "cfg-if" +-version = "1.0.1" ++version = "1.0.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" ++checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + + [[package]] + name = "clap" +@@ -124,7 +101,7 @@ dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", +- "indexmap", ++ "indexmap 1.9.3", + "strsim", + "termcolor", + "textwrap", +@@ -141,14 +118,14 @@ dependencies = [ + + [[package]] + name = "concread" +-version = "0.5.6" ++version = "0.5.10" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7b639eeaa550eba0c8be45b292d5e272e6d29bfdffb4df6925d651ed9ed10fd6" ++checksum = "6588e9e68e11207fb9a5aabd88765187969e6bcba98763c40bcad87b2a73e9f5" + dependencies = [ + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +- "foldhash", ++ "foldhash 0.2.0", + "lru", + "smallvec", + "sptr", +@@ -210,9 +187,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + + [[package]] + name = "errno" +-version = "0.3.12" ++version = "0.3.14" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" ++checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" + dependencies = [ + "libc", + "windows-sys", +@@ -232,17 +209,29 @@ checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f" + dependencies = [ + "base64", + "byteorder", +- "getrandom 0.2.16", ++ "getrandom 0.2.17", + "openssl", + "zeroize", + ] + ++[[package]] ++name = "find-msvc-tools" ++version = "0.1.9" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" ++ + [[package]] + name = "foldhash" + version = "0.1.5" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + ++[[package]] ++name = "foldhash" ++version = "0.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" ++ + [[package]] + name = "foreign-types" + version = "0.3.2" +@@ -260,32 +249,39 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + + [[package]] + name = "getrandom" +-version = "0.2.16" ++version = "0.2.17" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" ++checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" + dependencies = [ + "cfg-if", + "libc", +- "wasi 0.11.1+wasi-snapshot-preview1", ++ "wasi", + ] + + [[package]] + name = "getrandom" +-version = "0.3.3" ++version = "0.3.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" ++checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" + dependencies = [ + "cfg-if", + "libc", + "r-efi", +- "wasi 0.14.2+wasi-0.2.4", ++ "wasip2", + ] + + [[package]] +-name = "gimli" +-version = "0.31.1" ++name = "getrandom" ++version = "0.4.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" ++checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" ++dependencies = [ ++ "cfg-if", ++ "libc", ++ "r-efi", ++ "wasip2", ++ "wasip3", ++] + + [[package]] + name = "hashbrown" +@@ -295,13 +291,22 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + + [[package]] + name = "hashbrown" +-version = "0.15.4" ++version = "0.15.5" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" ++checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" ++dependencies = [ ++ "foldhash 0.1.5", ++] ++ ++[[package]] ++name = "hashbrown" ++version = "0.16.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + dependencies = [ + "allocator-api2", + "equivalent", +- "foldhash", ++ "foldhash 0.2.0", + ] + + [[package]] +@@ -310,6 +315,12 @@ version = "0.4.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + ++[[package]] ++name = "heck" ++version = "0.5.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" ++ + [[package]] + name = "hermit-abi" + version = "0.1.19" +@@ -319,6 +330,12 @@ dependencies = [ + "libc", + ] + ++[[package]] ++name = "id-arena" ++version = "2.3.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" ++ + [[package]] + name = "indexmap" + version = "1.9.3" +@@ -329,27 +346,45 @@ dependencies = [ + "hashbrown 0.12.3", + ] + ++[[package]] ++name = "indexmap" ++version = "2.13.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" ++dependencies = [ ++ "equivalent", ++ "hashbrown 0.16.1", ++ "serde", ++ "serde_core", ++] ++ + [[package]] + name = "itoa" +-version = "1.0.15" ++version = "1.0.17" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" ++checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + + [[package]] + name = "jobserver" +-version = "0.1.33" ++version = "0.1.34" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" ++checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" + dependencies = [ +- "getrandom 0.3.3", ++ "getrandom 0.3.4", + "libc", + ] + ++[[package]] ++name = "leb128fmt" ++version = "0.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" ++ + [[package]] + name = "libc" +-version = "0.2.174" ++version = "0.2.182" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" ++checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + + [[package]] + name = "librnsslapd" +@@ -372,48 +407,30 @@ dependencies = [ + + [[package]] + name = "linux-raw-sys" +-version = "0.9.4" ++version = "0.11.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" ++checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + + [[package]] + name = "log" +-version = "0.4.27" ++version = "0.4.29" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" ++checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + + [[package]] + name = "lru" +-version = "0.13.0" ++version = "0.16.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" ++checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" + dependencies = [ +- "hashbrown 0.15.4", ++ "hashbrown 0.16.1", + ] + + [[package]] + name = "memchr" +-version = "2.7.5" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +- +-[[package]] +-name = "miniz_oxide" +-version = "0.8.9" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +-dependencies = [ +- "adler2", +-] +- +-[[package]] +-name = "object" +-version = "0.36.7" ++version = "2.8.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +-dependencies = [ +- "memchr", +-] ++checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + + [[package]] + name = "once_cell" +@@ -423,11 +440,11 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + + [[package]] + name = "openssl" +-version = "0.10.73" ++version = "0.10.75" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" ++checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.11.0", + "cfg-if", + "foreign-types", + "libc", +@@ -444,14 +461,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" + dependencies = [ + "proc-macro2", + "quote", +- "syn 2.0.103", ++ "syn 2.0.117", + ] + + [[package]] + name = "openssl-sys" +-version = "0.9.109" ++version = "0.9.111" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" ++checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" + dependencies = [ + "cc", + "libc", +@@ -496,6 +513,16 @@ version = "0.3.32" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + ++[[package]] ++name = "prettyplease" ++version = "0.2.37" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" ++dependencies = [ ++ "proc-macro2", ++ "syn 2.0.117", ++] ++ + [[package]] + name = "proc-macro-hack" + version = "0.5.20+deprecated" +@@ -504,9 +531,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + + [[package]] + name = "proc-macro2" +-version = "1.0.95" ++version = "1.0.106" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" ++checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" + dependencies = [ + "unicode-ident", + ] +@@ -526,9 +553,9 @@ dependencies = [ + + [[package]] + name = "quote" +-version = "1.0.40" ++version = "1.0.44" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" ++checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" + dependencies = [ + "proc-macro2", + ] +@@ -539,19 +566,13 @@ version = "5.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +-[[package]] +-name = "rustc-demangle" +-version = "0.1.25" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +- + [[package]] + name = "rustix" +-version = "1.0.7" ++version = "1.1.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" ++checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", +@@ -559,41 +580,52 @@ dependencies = [ + ] + + [[package]] +-name = "ryu" +-version = "1.0.20" ++name = "semver" ++version = "1.0.27" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" ++checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + + [[package]] + name = "serde" +-version = "1.0.219" ++version = "1.0.228" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" ++checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" ++dependencies = [ ++ "serde_core", ++ "serde_derive", ++] ++ ++[[package]] ++name = "serde_core" ++version = "1.0.228" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" + dependencies = [ + "serde_derive", + ] + + [[package]] + name = "serde_derive" +-version = "1.0.219" ++version = "1.0.228" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" ++checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" + dependencies = [ + "proc-macro2", + "quote", +- "syn 2.0.103", ++ "syn 2.0.117", + ] + + [[package]] + name = "serde_json" +-version = "1.0.140" ++version = "1.0.149" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" ++checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" + dependencies = [ + "itoa", + "memchr", +- "ryu", + "serde", ++ "serde_core", ++ "zmij", + ] + + [[package]] +@@ -649,9 +681,9 @@ dependencies = [ + + [[package]] + name = "syn" +-version = "2.0.103" ++version = "2.0.117" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" ++checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" + dependencies = [ + "proc-macro2", + "quote", +@@ -660,12 +692,12 @@ dependencies = [ + + [[package]] + name = "tempfile" +-version = "3.20.0" ++version = "3.25.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" ++checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" + dependencies = [ + "fastrand", +- "getrandom 0.3.3", ++ "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys", +@@ -688,11 +720,10 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + + [[package]] + name = "tokio" +-version = "1.45.1" ++version = "1.49.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" ++checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" + dependencies = [ +- "backtrace", + "pin-project-lite", + ] + +@@ -707,9 +738,9 @@ dependencies = [ + + [[package]] + name = "tracing" +-version = "0.1.41" ++version = "0.1.44" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" ++checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" + dependencies = [ + "pin-project-lite", + "tracing-attributes", +@@ -718,29 +749,35 @@ dependencies = [ + + [[package]] + name = "tracing-attributes" +-version = "0.1.30" ++version = "0.1.31" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" ++checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" + dependencies = [ + "proc-macro2", + "quote", +- "syn 2.0.103", ++ "syn 2.0.117", + ] + + [[package]] + name = "tracing-core" +-version = "0.1.34" ++version = "0.1.36" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" ++checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + dependencies = [ + "once_cell", + ] + + [[package]] + name = "unicode-ident" +-version = "1.0.18" ++version = "1.0.24" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" ++checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" ++ ++[[package]] ++name = "unicode-xid" ++version = "0.2.6" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + + [[package]] + name = "uuid" +@@ -748,7 +785,7 @@ version = "0.8.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + dependencies = [ +- "getrandom 0.2.16", ++ "getrandom 0.2.17", + ] + + [[package]] +@@ -764,12 +801,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + + [[package]] +-name = "wasi" +-version = "0.14.2+wasi-0.2.4" ++name = "wasip2" ++version = "1.0.2+wasi-0.2.9" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" ++dependencies = [ ++ "wit-bindgen", ++] ++ ++[[package]] ++name = "wasip3" ++version = "0.4.0+wasi-0.3.0-rc-2026-01-06" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" ++dependencies = [ ++ "wit-bindgen", ++] ++ ++[[package]] ++name = "wasm-encoder" ++version = "0.244.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" ++dependencies = [ ++ "leb128fmt", ++ "wasmparser", ++] ++ ++[[package]] ++name = "wasm-metadata" ++version = "0.244.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" ++dependencies = [ ++ "anyhow", ++ "indexmap 2.13.0", ++ "wasm-encoder", ++ "wasmparser", ++] ++ ++[[package]] ++name = "wasmparser" ++version = "0.244.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" ++checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" + dependencies = [ +- "wit-bindgen-rt", ++ "bitflags 2.11.0", ++ "hashbrown 0.15.5", ++ "indexmap 2.13.0", ++ "semver", + ] + + [[package]] +@@ -790,9 +870,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + + [[package]] + name = "winapi-util" +-version = "0.1.9" ++version = "0.1.11" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" ++checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" + dependencies = [ + "windows-sys", + ] +@@ -804,103 +884,130 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + + [[package]] +-name = "windows-sys" +-version = "0.59.0" ++name = "windows-link" ++version = "0.2.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +-dependencies = [ +- "windows-targets", +-] ++checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + + [[package]] +-name = "windows-targets" +-version = "0.52.6" ++name = "windows-sys" ++version = "0.61.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" ++checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" + dependencies = [ +- "windows_aarch64_gnullvm", +- "windows_aarch64_msvc", +- "windows_i686_gnu", +- "windows_i686_gnullvm", +- "windows_i686_msvc", +- "windows_x86_64_gnu", +- "windows_x86_64_gnullvm", +- "windows_x86_64_msvc", ++ "windows-link", + ] + + [[package]] +-name = "windows_aarch64_gnullvm" +-version = "0.52.6" ++name = "wit-bindgen" ++version = "0.51.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +- +-[[package]] +-name = "windows_aarch64_msvc" +-version = "0.52.6" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +- +-[[package]] +-name = "windows_i686_gnu" +-version = "0.52.6" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +- +-[[package]] +-name = "windows_i686_gnullvm" +-version = "0.52.6" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" ++checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" ++dependencies = [ ++ "wit-bindgen-rust-macro", ++] + + [[package]] +-name = "windows_i686_msvc" +-version = "0.52.6" ++name = "wit-bindgen-core" ++version = "0.51.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" ++checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" ++dependencies = [ ++ "anyhow", ++ "heck 0.5.0", ++ "wit-parser", ++] + + [[package]] +-name = "windows_x86_64_gnu" +-version = "0.52.6" ++name = "wit-bindgen-rust" ++version = "0.51.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" ++checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" ++dependencies = [ ++ "anyhow", ++ "heck 0.5.0", ++ "indexmap 2.13.0", ++ "prettyplease", ++ "syn 2.0.117", ++ "wasm-metadata", ++ "wit-bindgen-core", ++ "wit-component", ++] + + [[package]] +-name = "windows_x86_64_gnullvm" +-version = "0.52.6" ++name = "wit-bindgen-rust-macro" ++version = "0.51.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" ++checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" ++dependencies = [ ++ "anyhow", ++ "prettyplease", ++ "proc-macro2", ++ "quote", ++ "syn 2.0.117", ++ "wit-bindgen-core", ++ "wit-bindgen-rust", ++] + + [[package]] +-name = "windows_x86_64_msvc" +-version = "0.52.6" ++name = "wit-component" ++version = "0.244.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" ++checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" ++dependencies = [ ++ "anyhow", ++ "bitflags 2.11.0", ++ "indexmap 2.13.0", ++ "log", ++ "serde", ++ "serde_derive", ++ "serde_json", ++ "wasm-encoder", ++ "wasm-metadata", ++ "wasmparser", ++ "wit-parser", ++] + + [[package]] +-name = "wit-bindgen-rt" +-version = "0.39.0" ++name = "wit-parser" ++version = "0.244.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" ++checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" + dependencies = [ +- "bitflags 2.9.1", ++ "anyhow", ++ "id-arena", ++ "indexmap 2.13.0", ++ "log", ++ "semver", ++ "serde", ++ "serde_derive", ++ "serde_json", ++ "unicode-xid", ++ "wasmparser", + ] + + [[package]] + name = "zeroize" +-version = "1.8.1" ++version = "1.8.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" ++checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + dependencies = [ + "zeroize_derive", + ] + + [[package]] + name = "zeroize_derive" +-version = "1.4.2" ++version = "1.4.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" ++checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" + dependencies = [ + "proc-macro2", + "quote", +- "syn 2.0.103", ++ "syn 2.0.117", + ] ++ ++[[package]] ++name = "zmij" ++version = "1.0.21" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +diff --git a/src/librslapd/Cargo.toml b/src/librslapd/Cargo.toml +index f38f93f4c..61100f381 100644 +--- a/src/librslapd/Cargo.toml ++++ b/src/librslapd/Cargo.toml +@@ -16,7 +16,7 @@ crate-type = ["staticlib", "lib"] + [dependencies] + slapd = { path = "../slapd" } + libc = "0.2" +-concread = "0.5.6" ++concread = "0.5.10" + + [build-dependencies] + cbindgen = "0.26" +-- +2.53.0 + diff --git a/SOURCES/0035-Issue-7271-plugins-that-create-threads-need-to-updat.patch b/SOURCES/0035-Issue-7271-plugins-that-create-threads-need-to-updat.patch new file mode 100644 index 0000000..0bbd969 --- /dev/null +++ b/SOURCES/0035-Issue-7271-plugins-that-create-threads-need-to-updat.patch @@ -0,0 +1,86 @@ +From a3fd3897ede4166625194d48b010b6dbdcdf9f70 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 23 Feb 2026 12:37:26 -0500 +Subject: [PATCH] Issue 7271 - plugins that create threads need to update + active thread count + +Description: + +Plugins that create threads need to up to the global active thread count. +Otherwise when the server is being stopped the plugin's close function gets +called while these threads are still running and still using the plugin +configuration. This can lead to crashes. + +relates: https://github.com/389ds/389-ds-base/issues/7271 + +Reviewed by: progier & tbordaz (Thanks!!) +--- + ldap/servers/plugins/replication/repl5_protocol.c | 5 +++++ + ldap/servers/plugins/retrocl/retrocl_trim.c | 7 ++++++- + 2 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c +index 5d4a0e455..bc9580319 100644 +--- a/ldap/servers/plugins/replication/repl5_protocol.c ++++ b/ldap/servers/plugins/replication/repl5_protocol.c +@@ -23,6 +23,7 @@ + + #include "repl5.h" + #include "repl5_prot_private.h" ++#include "slap.h" + + #define PROTOCOL_5_INCREMENTAL 1 + #define PROTOCOL_5_TOTAL 2 +@@ -237,6 +238,8 @@ prot_thread_main(void *arg) + return; + } + ++ g_incr_active_threadcnt(); ++ + set_thread_private_agmtname(agmt_get_long_name(agmt)); + + done = 0; +@@ -301,6 +304,8 @@ prot_thread_main(void *arg) + done = 1; + } + } ++ ++ g_decr_active_threadcnt(); + } + + /* +diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c +index 8fcd3d32b..8fdc75c62 100644 +--- a/ldap/servers/plugins/retrocl/retrocl_trim.c ++++ b/ldap/servers/plugins/retrocl/retrocl_trim.c +@@ -243,6 +243,8 @@ trim_changelog(void) + + now_interval = slapi_current_rel_time_t(); /* monotonic time for interval */ + ++ g_incr_active_threadcnt(); ++ + PR_Lock(ts.ts_s_trim_mutex); + max_age = ts.ts_c_max_age; + trim_interval = ts.ts_c_trim_interval; +@@ -257,7 +259,7 @@ trim_changelog(void) + */ + done = 0; + now_maxage = slapi_current_utc_time(); /* real time for trim candidates */ +- while (!done && retrocl_trimming == 1) { ++ while (!done && retrocl_trimming == 1 && !slapi_is_shutting_down()) { + int did_delete; + + did_delete = 0; +@@ -309,6 +311,9 @@ trim_changelog(void) + "trim_changelog: removed %d change records\n", + num_deleted); + } ++ ++ g_decr_active_threadcnt(); ++ + return rc; + } + +-- +2.53.0 + diff --git a/SOURCES/0036-Security-fix-for-CVE-2025-14905.patch b/SOURCES/0036-Security-fix-for-CVE-2025-14905.patch new file mode 100644 index 0000000..d889f57 --- /dev/null +++ b/SOURCES/0036-Security-fix-for-CVE-2025-14905.patch @@ -0,0 +1,93 @@ +From f4817944e43e080b1ab15c3fa13359252429e500 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Wed, 25 Feb 2026 14:06:42 +0100 +Subject: [PATCH] Security fix for CVE-2025-14905 + +Description: + A vulnerability was found in the 389 Directory Server. + The 389 Directory Server present a risk of heap buffer overflow that + can be exploited to excute a Denial of Service and potential Remote + Code Execution + +References: + - https://access.redhat.com/security/cve/CVE-2025-14905 + - https://bugzilla.redhat.com/show_bug.cgi?id=2423624 +--- + ldap/servers/slapd/schema.c | 47 ++++++++++++++++++++++++++++++------- + 1 file changed, 38 insertions(+), 9 deletions(-) + +diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c +index 9ef4ee4bf..7712a720d 100644 +--- a/ldap/servers/slapd/schema.c ++++ b/ldap/servers/slapd/schema.c +@@ -1410,6 +1410,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg) + const char *attr_desc, *syntaxoid; + char *outp, syntaxlengthbuf[128]; + int i; ++ int nb_aliases = 0; + + vals[0] = &val; + +@@ -1435,6 +1436,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg) + if (asip->asi_aliases != NULL) { + for (i = 0; asip->asi_aliases[i] != NULL; ++i) { + aliaslen += strlen(asip->asi_aliases[i]); ++ nb_aliases++; + } + } + +@@ -1452,15 +1454,42 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg) + * XXX: 256 is a magic number... it must be big enough to account for + * all of the fixed sized items we output. + */ +- sizedbuffer_allocate(aew->psbAttrTypes, 256 + strlen(asip->asi_oid) + +- strlen(asip->asi_name) + +- aliaslen + strlen_null_ok(attr_desc) + +- strlen(syntaxoid) + +- strlen_null_ok(asip->asi_superior) + +- strlen_null_ok(asip->asi_mr_equality) + +- strlen_null_ok(asip->asi_mr_ordering) + +- strlen_null_ok(asip->asi_mr_substring) + +- strcat_extensions(NULL, asip->asi_extensions)); ++ { ++ int asi_oid_strlen = strlen(asip->asi_oid) + 8; /* "( %s NAME " */ ++ int asi_name_strlen = strlen(asip->asi_name) + 6; /* "( '%s' ...)" */ ++ int asi_aliases_strlen = aliaslen + nb_aliases * 3; /* "'%s' " */ ++ int asi_desc_strlen = strlen_null_ok(attr_desc) + 7; /* "DESC '%s'" */ ++ int asi_syntaxoid_strlen = strlen("SYNTAX ") + strlen(syntaxoid) + strlen(syntaxlengthbuf); ++ int asi_superior_strlen = strlen("SUP ") + strlen_null_ok(asip->asi_superior); ++ int asi_mr_equality_strlen = strlen("EQUALITY ") + strlen_null_ok(asip->asi_mr_equality); ++ int asi_mr_ordering_strlen = strlen("ORDERING ") + strlen_null_ok(asip->asi_mr_ordering); ++ int asi_mr_substring_strlen = strlen("SUBSTR ") + strlen_null_ok(asip->asi_mr_substring); ++ int asi_flags_strlen = strlen("SINGLE-VALUE ") + ++ strlen(schema_obsolete_with_spaces) + ++ strlen(schema_collective_with_spaces) + ++ strlen(schema_nousermod_with_spaces) + ++ strlen("USAGE distributedOperation ") + ++ strlen("USAGE dSAOperation ") + ++ strlen("USAGE directoryOperation "); ++ int asi_extension_strlen = strcat_extensions(NULL, asip->asi_extensions); ++ ++ if (aew->enquote_sup_oc) { ++ /* it enquote the syntax oid */ ++ asi_syntaxoid_strlen += 2; ++ } ++ ++ sizedbuffer_allocate(aew->psbAttrTypes, 256 + asi_oid_strlen + ++ asi_name_strlen + ++ asi_aliases_strlen + ++ asi_desc_strlen + ++ asi_syntaxoid_strlen + ++ asi_superior_strlen + ++ asi_mr_equality_strlen + ++ asi_mr_ordering_strlen + ++ asi_mr_substring_strlen + ++ asi_extension_strlen + ++ asi_flags_strlen); ++ } + + /* + * Overall strategy is to maintain a pointer to the next location in +-- +2.53.0 + diff --git a/SOURCES/0037-Issue-7271-implement-a-pre-close-plugin-function.patch b/SOURCES/0037-Issue-7271-implement-a-pre-close-plugin-function.patch new file mode 100644 index 0000000..cda832c --- /dev/null +++ b/SOURCES/0037-Issue-7271-implement-a-pre-close-plugin-function.patch @@ -0,0 +1,262 @@ +From c6bb96c95dcbff0052fc2decab7020b0f833f313 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 2 Mar 2026 14:31:29 -0500 +Subject: [PATCH] Issue 7271 - implement a pre-close plugin function + +Description: + +replication protocol could benefit from being notifioed the shutdown process +has started before calling the "close" plugin function. This would allow the +replication protocol to wake up and adjust its active thread count to prevent +a hang during shutdown. + +relates: https://github.com/389ds/389-ds-base/issues/7271 + +Reviewed by: progier, spichugi, and vashirov(Thanks!!!) +--- + ldap/servers/plugins/replication/repl5_init.c | 19 ++++++++---- + ldap/servers/slapd/daemon.c | 6 +++- + ldap/servers/slapd/pblock.c | 30 +++++++++++++------ + ldap/servers/slapd/plugin.c | 24 ++++++++++++++- + ldap/servers/slapd/proto-slap.h | 3 +- + ldap/servers/slapd/slap.h | 3 +- + ldap/servers/slapd/slapi-plugin.h | 3 +- + 7 files changed, 69 insertions(+), 19 deletions(-) + +diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c +index 5047fb8dc..395744db8 100644 +--- a/ldap/servers/plugins/replication/repl5_init.c ++++ b/ldap/servers/plugins/replication/repl5_init.c +@@ -1,6 +1,6 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2005 Red Hat, Inc. ++ * Copyright (C) 2026 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -122,6 +122,8 @@ static PRUintn thread_private_agmtname; /* thread private index for logging*/ + static PRUintn thread_private_cache; + static PRUintn thread_primary_csn; + ++static int multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused))); ++ + char * + get_thread_private_agmtname() + { +@@ -836,10 +838,6 @@ multisupplier_stop(Slapi_PBlock *pb __attribute__((unused))) + int rc = 0; /* OK */ + + if (!multisupplier_stopped_flag) { +- if (!is_ldif_dump) { +- /* Shut down replication agreements */ +- agmtlist_shutdown(); +- } + /* if we are cleaning a ruv, stop */ + stop_ruv_cleaning(); + /* unregister backend state change notification */ +@@ -853,6 +851,16 @@ multisupplier_stop(Slapi_PBlock *pb __attribute__((unused))) + return rc; + } + ++static int ++multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused))) ++{ ++ if (!multisupplier_stopped_flag) { ++ /* Shut down replication agreements which will stop all the ++ * replication protocol threads */ ++ agmtlist_shutdown(); ++ } ++ return 0; ++} + + PRBool + multisupplier_started() +@@ -900,6 +908,7 @@ replication_multisupplier_plugin_init(Slapi_PBlock *pb) + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multisupplierdesc); + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)multisupplier_start); + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)multisupplier_stop); ++ rc = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_CLOSE_FN, (void *)multisupplier_pre_stop); + + /* Register the plugin interfaces we implement */ + /* preop acquires csn generator handle */ +diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c +index d6911f03c..7fc1a16f1 100644 +--- a/ldap/servers/slapd/daemon.c ++++ b/ldap/servers/slapd/daemon.c +@@ -1,6 +1,6 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2021 Red Hat, Inc. ++ * Copyright (C) 2026 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -1216,6 +1216,10 @@ slapd_daemon(daemon_ports_t *ports) + task_cancel_all(); + } + ++ /* Call plugin pre close functions */ ++ plugin_pre_closeall(); ++ ++ /* Now wait for active threads to terminate */ + threads = g_get_active_threadcnt(); + if (threads > 0) { + slapi_log_err(SLAPI_LOG_INFO, "slapd_daemon", +diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c +index 76e26cb86..90114dee1 100644 +--- a/ldap/servers/slapd/pblock.c ++++ b/ldap/servers/slapd/pblock.c +@@ -1,12 +1,12 @@ +-/** BEGIN COPYRIGHT BLOCK +- * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2005-2025 Red Hat, Inc. +- * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. +- * All rights reserved. +- * +- * License: GPL (version 3 or any later version). +- * See LICENSE for details. +- * END COPYRIGHT BLOCK **/ ++ /** BEGIN COPYRIGHT BLOCK ++ * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. ++ * Copyright (C) 2026 Red Hat, Inc. ++ * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. ++ * All rights reserved. ++ * ++ * License: GPL (version 3 or any later version). ++ * See LICENSE for details. ++ * END COPYRIGHT BLOCK **/ + + #ifdef HAVE_CONFIG_H + #include +@@ -2524,6 +2524,14 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + } + break; + ++ case SLAPI_PLUGIN_PRE_CLOSE_FN: ++ if (pblock->pb_plugin != NULL) { ++ (*(IFP *)value) = pblock->pb_plugin->plg_pre_close; ++ } else { ++ (*(IFP *)value) = NULL; ++ } ++ break; ++ + default: + slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_get", "Unknown parameter block argument %d\n", arg); + PR_ASSERT(0); +@@ -4209,6 +4217,10 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_misc->pb_aci_target_check = *((int *)value); + break; + ++ case SLAPI_PLUGIN_PRE_CLOSE_FN: ++ pblock->pb_plugin->plg_pre_close = (IFP)value; ++ break; ++ + default: + slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_set", + "Unknown parameter block argument %d\n", arg); +diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c +index 52d25e19e..95b8de894 100644 +--- a/ldap/servers/slapd/plugin.c ++++ b/ldap/servers/slapd/plugin.c +@@ -1,6 +1,6 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2021 Red Hat, Inc. ++ * Copyright (C) 2026 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -1847,6 +1847,28 @@ plugin_dependency_closeall(void) + } + } + ++/* Call the pre close functions of all the plugins */ ++void ++plugin_pre_closeall(void) ++{ ++ Slapi_PBlock *pb = NULL; ++ int plugins_pre_closed = 0; ++ int index = 0; ++ ++ while (plugins_pre_closed < global_plugins_started) { ++ if (global_plugin_shutdown_order[index].name) { ++ if (!global_plugin_shutdown_order[index].removed) { ++ pb = slapi_pblock_new(); ++ plugin_call_one(global_plugin_shutdown_order[index].plugin, ++ SLAPI_PLUGIN_PRE_CLOSE_FN, pb); ++ slapi_pblock_destroy(pb); ++ } ++ plugins_pre_closed++; ++ } ++ index++; ++ } ++} ++ + void + plugin_freeall(void) + { +diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h +index 742e3ee14..fd5bc6e71 100644 +--- a/ldap/servers/slapd/proto-slap.h ++++ b/ldap/servers/slapd/proto-slap.h +@@ -1,6 +1,6 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2021-2025 Red Hat, Inc. ++ * Copyright (C) 2026 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -982,6 +982,7 @@ int plugin_call_exop_plugins(Slapi_PBlock *pb, struct slapdplugin *p); + Slapi_Backend *plugin_extended_op_getbackend(Slapi_PBlock *pb, struct slapdplugin *p); + const char *plugin_extended_op_oid2string(const char *oid); + void plugin_closeall(int close_backends, int close_globals); ++void plugin_pre_closeall(void); + void plugin_dependency_freeall(void); + void plugin_startall(int argc, char **argv, char **plugin_list); + void plugin_get_plugin_dependencies(char *plugin_name, char ***names); +diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h +index fee5a6ab5..64f9f465b 100644 +--- a/ldap/servers/slapd/slap.h ++++ b/ldap/servers/slapd/slap.h +@@ -1,7 +1,7 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. +- * Copyright (C) 2009-2025 Red Hat, Inc. ++ * Copyright (C) 2026 Red Hat, Inc. + * All rights reserved. + * + * Contributors: +@@ -1035,6 +1035,7 @@ struct slapdplugin + char *plg_libpath; /* library path for dll/so */ + char *plg_initfunc; /* init symbol */ + IFP plg_close; /* close function */ ++ IFP plg_pre_close; /* pre close function */ + Slapi_PluginDesc plg_desc; /* vendor's info */ + char *plg_name; /* used for plugin rdn in cn=config */ + struct slapdplugin *plg_next; /* for plugin lists */ +diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h +index 5d9ef289d..4661d2cf0 100644 +--- a/ldap/servers/slapd/slapi-plugin.h ++++ b/ldap/servers/slapd/slapi-plugin.h +@@ -1,6 +1,6 @@ + /* BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2021 Red Hat, Inc. ++ * Copyright (C) 2026 Red Hat, Inc. + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * All rights reserved. + * +@@ -7082,6 +7082,7 @@ typedef struct slapi_plugindesc + + /* miscellaneous plugin functions */ + #define SLAPI_PLUGIN_CLOSE_FN 210 ++#define SLAPI_PLUGIN_PRE_CLOSE_FN 211 + #define SLAPI_PLUGIN_START_FN 212 + #define SLAPI_PLUGIN_CLEANUP_FN 232 + #define SLAPI_PLUGIN_POSTSTART_FN 233 +-- +2.53.0 + diff --git a/SOURCES/0038-Issue-7271-Add-new-plugin-pre-close-function-check-t.patch b/SOURCES/0038-Issue-7271-Add-new-plugin-pre-close-function-check-t.patch new file mode 100644 index 0000000..e3f43c0 --- /dev/null +++ b/SOURCES/0038-Issue-7271-Add-new-plugin-pre-close-function-check-t.patch @@ -0,0 +1,34 @@ +From 21b9e00264437dff3b6b81355cd159485bfdaf37 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 4 Mar 2026 14:47:01 -0500 +Subject: [PATCH] Issue 7271 - Add new plugin pre-close function check to + plugin_invoke_plugin_pb + +Description: + +In plugin_invoke_plugin_pb we were not checking for the new pre-close function +which led to an error in the logs: pb_op is NULL. In a debug build this leads +to an assertion error at shutdown. + +relates: https://github.com/389ds/389-ds-base/issues/7271 + +Reviewed by: vashirov(Thanks!) +--- + ldap/servers/slapd/plugin.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c +index 95b8de894..c1b0571eb 100644 +--- a/ldap/servers/slapd/plugin.c ++++ b/ldap/servers/slapd/plugin.c +@@ -3647,6 +3647,7 @@ plugin_invoke_plugin_pb(struct slapdplugin *plugin, int operation, Slapi_PBlock + if (operation == SLAPI_PLUGIN_START_FN || + operation == SLAPI_PLUGIN_POSTSTART_FN || + operation == SLAPI_PLUGIN_CLOSE_FN || ++ operation == SLAPI_PLUGIN_PRE_CLOSE_FN || + operation == SLAPI_PLUGIN_CLEANUP_FN || + operation == SLAPI_PLUGIN_BE_PRE_CLOSE_FN || + operation == SLAPI_PLUGIN_BE_POST_OPEN_FN || +-- +2.53.0 + diff --git a/SOURCES/389-ds-base-git.sh b/SOURCES/389-ds-base-git.sh deleted file mode 100644 index 0043901..0000000 --- a/SOURCES/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/SOURCES/Cargo-2.7.0-1.lock b/SOURCES/Cargo-2.8.0-5.lock similarity index 59% rename from SOURCES/Cargo-2.7.0-1.lock rename to SOURCES/Cargo-2.8.0-5.lock index 49eacc7..8d9aca8 100644 --- a/SOURCES/Cargo-2.7.0-1.lock +++ b/SOURCES/Cargo-2.8.0-5.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "allocator-api2" version = "0.2.21" @@ -40,21 +25,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base64" version = "0.13.1" @@ -69,9 +39,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "byteorder" @@ -100,10 +70,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.31" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -111,9 +82,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" @@ -141,9 +112,9 @@ dependencies = [ [[package]] name = "concread" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fd8c4b53f0aafeec114fa1cd863f323880f790656f2d7508af83a9b5110e8d" +checksum = "6588e9e68e11207fb9a5aabd88765187969e6bcba98763c40bcad87b2a73e9f5" dependencies = [ "crossbeam-epoch", "crossbeam-queue", @@ -210,12 +181,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -238,10 +209,16 @@ dependencies = [ ] [[package]] -name = "foldhash" -version = "0.1.5" +name = "find-msvc-tools" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-types" @@ -266,27 +243,21 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "hashbrown" version = "0.12.3" @@ -295,9 +266,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -329,38 +300,27 @@ dependencies = [ "hashbrown 0.12.3", ] -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "libc" -version = "0.2.174" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "librnsslapd" @@ -383,59 +343,30 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.13.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.16.1", ] [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "once_cell" @@ -445,11 +376,11 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -466,14 +397,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -526,9 +457,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -548,9 +479,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -561,61 +492,60 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] -name = "ryu" -version = "1.0.20" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] [[package]] -name = "serde" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -624,12 +554,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - [[package]] name = "slapd" version = "0.1.0" @@ -677,9 +601,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -688,15 +612,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -716,16 +640,11 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", - "io-uring", - "libc", - "mio", "pin-project-lite", - "slab", ] [[package]] @@ -739,9 +658,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -750,29 +669,29 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "uuid" @@ -796,12 +715,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -822,11 +741,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -837,182 +756,47 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec index cc48695..fead9da 100644 --- a/SPECS/389-ds-base.spec +++ b/SPECS/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: 6%{?dist} +License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND MIT AND MPL-2.0 AND Zlib URL: https://www.port389.org Conflicts: selinux-policy-base < 3.9.8 Conflicts: freeipa-server < 4.0.3 @@ -58,103 +58,87 @@ 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 +Provides: bundled(crate(concread)) = 0.5.10 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(foldhash)) = 0.1.5 +Provides: bundled(crate(find-msvc-tools)) = 0.1.7 +Provides: bundled(crate(foldhash)) = 0.2.0 Provides: bundled(crate(foreign-types)) = 0.3.2 Provides: bundled(crate(foreign-types-shared)) = 0.1.1 -Provides: bundled(crate(getrandom)) = 0.3.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.16.1 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(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(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.16.3 +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 ##### Bundled cargo crates list - END ##### BuildRequires: nspr-devel >= 4.32 @@ -277,51 +261,54 @@ 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 %endif Source4: 389-ds-base.sysusers -Source5: vendor-%{version}-1.tar.gz -Source6: Cargo-%{version}-1.lock +Source5: vendor-%{version}-5.tar.gz +Source6: Cargo-%{version}-5.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 +Patch: 0005-Issue-7172-2nd-Index-ordering-mismatch-after-upgrade.patch +Patch: 0006-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch +Patch: 0007-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch +Patch: 0008-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch +Patch: 0009-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch +Patch: 0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch +Patch: 0011-Issue-6476-Fix-build-failure-with-GCC-15.patch +Patch: 0012-Issue-7223-Revert-index-scan-limits-for-system-index.patch +Patch: 0013-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch +Patch: 0014-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch +Patch: 0015-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch +Patch: 0016-Issue-7223-Add-dsctl-index-check-command-for-offline.patch +Patch: 0017-Issue-7096-2nd-During-replication-online-total-init-.patch +Patch: 0018-Issue-7076-6992-6784-6214-Fix-CI-test-failures-7077.patch +Patch: 0019-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch +Patch: 0020-Issue-6947-Fix-health_system_indexes_test.py.patch +Patch: 0021-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch +Patch: 0022-Issue-7150-Compressed-access-log-rotations-skipped-a.patch +Patch: 0023-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch +Patch: 0024-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch +Patch: 0025-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch +Patch: 0026-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch +Patch: 0027-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch +Patch: 0028-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch +Patch: 0029-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch +Patch: 0030-Issue-7066-7052-allow-password-history-to-be-set-to-.patch +Patch: 0031-Issue-7243-UI-fix-certificate-table-and-modal.patch +Patch: 0032-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch +Patch: 0033-Issue-7053-Remove-memberof_del_dn_from_groups-from-M.patch +Patch: 0034-Issue-5853-Update-concread-to-0.5.10.patch +Patch: 0035-Issue-7271-plugins-that-create-threads-need-to-updat.patch +Patch: 0036-Security-fix-for-CVE-2025-14905.patch +Patch: 0037-Issue-7271-implement-a-pre-close-plugin-function.patch +Patch: 0038-Issue-7271-Add-new-plugin-pre-close-function-check-t.patch %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -425,9 +412,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 @@ -595,40 +586,39 @@ fi # Reload our sysctl before we restart (if we can) sysctl --system &> $output; true -# Gather the running instances so we can restart them +# Gather running instances, stop them, then restart instbase="%{_sysconfdir}/%{pkgname}" +instances="" ninst=0 -for dir in $instbase/slapd-* ; do - echo dir = $dir >> $output 2>&1 || : + +for dir in "$instbase"/slapd-* ; do + echo "dir = $dir" >> "$output" 2>&1 || : if [ ! -d "$dir" ] ; then continue ; fi case "$dir" in *.removed) continue ;; esac - basename=`basename $dir` - inst="%{pkgname}@`echo $basename | sed -e 's/slapd-//g'`" - echo found instance $inst - getting status >> $output 2>&1 || : - if /bin/systemctl -q is-active $inst ; then - echo instance $inst is running >> $output 2>&1 || : + basename=$(basename "$dir") + inst="%{pkgname}@${basename#slapd-}" + inst_name="${basename#slapd-}" + echo "found instance $inst - getting status" >> "$output" 2>&1 || : + if /bin/systemctl -q is-active "$inst" ; then + echo "instance $inst is running - stopping for upgrade" >> "$output" 2>&1 || : instances="$instances $inst" + /bin/systemctl stop "$inst" >> "$output" 2>&1 || : else - echo instance $inst is not running >> $output 2>&1 || : + echo "instance $inst is not running" >> "$output" 2>&1 || : fi - ninst=`expr $ninst + 1` + ninst=$((ninst + 1)) done + if [ $ninst -eq 0 ] ; then - echo no instances to upgrade >> $output 2>&1 || : - exit 0 # have no instances to upgrade - just skip the rest -else - # restart running instances - echo shutting down all instances . . . >> $output 2>&1 || : - for inst in $instances ; do - echo stopping instance $inst >> $output 2>&1 || : - /bin/systemctl stop $inst >> $output 2>&1 || : - done - for inst in $instances ; do - echo starting instance $inst >> $output 2>&1 || : - /bin/systemctl start $inst >> $output 2>&1 || : - done + echo "no instances to upgrade" >> "$output" 2>&1 || : + exit 0 fi +# Restart previously running instances +for inst in $instances ; do + echo "starting instance $inst" >> "$output" 2>&1 || : + /bin/systemctl start "$inst" >> "$output" 2>&1 || : +done %preun if [ $1 -eq 0 ]; then # Final removal @@ -768,6 +758,61 @@ exit 0 %endif %changelog +* Thu Mar 05 2026 Viktor Ashirov - 2.8.0-6 +- Resolves: RHEL-152335 - Crash in trim_changelog() during the Retro Changelog trimming. [rhel-9.8] + +* Fri Feb 27 2026 Viktor Ashirov - 2.8.0-5 +- Resolves: RHEL-137084 - CVE-2025-14905 389-ds-base: 389-ds-base: Remote Code Execution and Denial of Service via heap buffer overflow [rhel-9.8] +- Resolves: RHEL-152335 - Crash in trim_changelog() during the Retro Changelog trimming. [rhel-9.8] +- Resolves: RHEL-152338 - Crash ( Segmentation fault ) in atomic_compare_exchange() [rhel-9.8] + +* Fri Feb 20 2026 Viktor Ashirov - 2.8.0-4 +- Resolves: RHEL-117050 - Replication online reinitialization of a large database gets stalled. [rhel-9] +- Resolves: RHEL-123279 - The new ipahealthcheck test ipahealthcheck.ds.backends.BackendsCheck raises CRITICAL issue [rhel-9] +- Resolves: RHEL-140275 - ipa-healthcheck is complaining about missing or incorrectly configured system indexes. [rhel-9] +- Resolves: RHEL-142980 - Scalability issue of replication online initialization with large database [rhel-9] +- Resolves: RHEL-146899 - memory corruption in alias entry plugin [rhel-9] +- Resolves: RHEL-147212 - Access logs are not getting deleted as configured. [rhel-9] +- Resolves: RHEL-150907 - Remove memberof_del_dn_from_groups from MemberOf plugin [rhel-9] + +* Thu Feb 12 2026 Viktor Ashirov - 2.8.0-3 +- Resolves: RHEL-117050 - Replication online reinitialization of a large database gets stalled. [rhel-9] +- Resolves: RHEL-123244 - Attribute uniqueness is not enforced upon modrdn operation [rhel-9] +- Resolves: RHEL-123279 - The new ipahealthcheck test ipahealthcheck.ds.backends.BackendsCheck raises CRITICAL issue [rhel-9] +- Resolves: RHEL-140275 - ipa-healthcheck is complaining about missing or incorrectly configured system indexes. [rhel-9] +- Resolves: RHEL-142980 - Scalability issue of replication online initialization with large database [rhel-9] +- Resolves: RHEL-146899 - memory corruption in alias entry plugin [rhel-9] +- Resolves: RHEL-147212 - Access logs are not getting deleted as configured. [rhel-9] + +* Mon Jan 12 2026 Viktor Ashirov - 2.8.0-2 +- Resolves: RHEL-140089 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication [rhel-9] + +* 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]