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 new file mode 100644 index 0000000..154475e --- /dev/null +++ b/SOURCES/0030-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch @@ -0,0 +1,268 @@ +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/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch b/SOURCES/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch new file mode 100644 index 0000000..4c84c24 --- /dev/null +++ b/SOURCES/0031-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch @@ -0,0 +1,1448 @@ +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/0032-Issue-6910-Fix-latest-coverity-issues.patch b/SOURCES/0032-Issue-6910-Fix-latest-coverity-issues.patch new file mode 100644 index 0000000..7f9f8d5 --- /dev/null +++ b/SOURCES/0032-Issue-6910-Fix-latest-coverity-issues.patch @@ -0,0 +1,559 @@ +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/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 new file mode 100644 index 0000000..f3dbf20 --- /dev/null +++ b/SOURCES/0033-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch @@ -0,0 +1,35 @@ +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/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec index 617c251..cc48695 100644 --- a/SPECS/389-ds-base.spec +++ b/SPECS/389-ds-base.spec @@ -47,7 +47,7 @@ ExcludeArch: i686 Summary: 389 Directory Server (base) Name: 389-ds-base Version: 2.7.0 -Release: 5%{?dist} +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 URL: https://www.port389.org Conflicts: selinux-policy-base < 3.9.8 @@ -318,6 +318,10 @@ 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 %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -764,6 +768,16 @@ exit 0 %endif %changelog +* 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] +- Resolves: RHEL-109034 - Allow Uniqueness plugin to search uniqueness attributes using custom matching rules [rhel-9] +- Resolves: RHEL-109885 - Wrong backend database name syntax causes "Red Hat Directory Server" => "Databases" menu blank in Cockpit [rhel-9] +- Resolves: RHEL-109889 - RootDN Access Control Plugin with wildcards for IP addresses fails with an error "Invalid IP address" [rhel-9] +- Resolves: RHEL-109892 - On RHDS 12.6 The user password policy for a user was created, but the pwdpolicysubentry attribute for this user incorrectly points to the People OU password policy instead of the specific user policy. [rhel-9] +- Resolves: RHEL-109897 - AddressSanitizer: leak in do_search [rhel-9] +- Resolves: RHEL-113981 - AddressSanitizer: memory leak in memberof_add_memberof_attr [rhel-9] + * Tue Aug 05 2025 Viktor Ashirov - 2.7.0-5 - Resolves: RHEL-89762 - dsidm Error: float() argument must be a string or a number, not 'NoneType' [rhel-9] - Resolves: RHEL-92041 - Memory leak in roles_cache_create_object_from_entry