From patchwork Mon Nov 24 19:47:36 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125188 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 695533858407 for ; Mon, 24 Nov 2025 19:50:13 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 695533858407 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=i+amV2Ra X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 833CC3858416 for ; Mon, 24 Nov 2025 19:47:44 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 833CC3858416 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 833CC3858416 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013664; cv=none; b=ok//U0E6RsK7CpHb+SrbBn8nAVQnoZQ1CIRYC7z06mq9xB+G8i0xkgE7DkJ8w1I2HoU9Qvb/8MaBhsmLjbDR9jH6ku+jPYlpcg6/ZavPHc6KKugout7NVQaDIW/yp5+EDwv+3HziQMbxi5oHTr3kQdrCXD1IYaiWDpqu9dcysLY= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013664; c=relaxed/simple; bh=KxwQ3TTRYtopyPxLnOFa3DLYDEjEI6wX/KVSXz1A19o=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=MWvdS4HP2XsinP49Xwk2O/XFM9LtKl0hVn547Ax43yi3TBhv9mDf7rFpq9PqLKNWZRPXVspF9eGN7UID4sNLJD0gzU/MK7/wW3lIU2Nzp9civmsAExUzv0645yuuAT84PqYIvC/o6srLgfae/td/WAV/LG7M8B6UbxygW5RI+6o= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 833CC3858416 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013664; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=x0eDGHtTog6rLX6CuJLMxl+B87WnBftJfbARY29k9wI=; b=i+amV2Raop7JqrmMP613itE35qiiGWbZ2EYRgoFf4gz8N9yZuI5ow7yUPKWbRP5Bgo/Z3Y bh5tZO5eFepndL+8WWAHxG5LWZk/2k51nCW543gcjge7pBajSx2AWn02K+tHgBG/qKz6kb CC7Pp2G9p7WhWnDTCZxGTAJGUTrNVp4= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-643-KNDWzm7eMFKSpmvRShhGJA-1; Mon, 24 Nov 2025 14:47:40 -0500 X-MC-Unique: KNDWzm7eMFKSpmvRShhGJA-1 X-Mimecast-MFC-AGG-ID: KNDWzm7eMFKSpmvRShhGJA_1764013660 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0EBD11956052 for ; Mon, 24 Nov 2025 19:47:40 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1B80A18004D8 for ; Mon, 24 Nov 2025 19:47:38 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 1/7] nscd: Fix most data races in client retry counters (bug 33654) In-Reply-To: Message-ID: References: X-From-Line: fdbe2038d8400d7f2dcb584d4cc55f3949a50ea3 Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:47:36 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 84zyEkXsVJy1Tpt-IwFgZCZEk_mzOYpsTLSsIuAq2Jc_1764013660 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org Merge the individual __nss_not_use_nscd_* variables and the __nss_database_custom variable into a new __nscd_skip_counters variable that serves both purposes. Unify the database counter checks and updates into a separate function, __nscd_use_database. Individual client functions now call __nscd_defer_database to defer nscd use on certain errors. To disable nscd for databases due to custom configuration, __nscd_disable_database can be used. This partially fixes bug 33654 because __nscd_use_database uses atomic updates. --- nscd/Makefile | 6 +++- nscd/nscd_getai.c | 10 ++----- nscd/nscd_getgr_r.c | 6 ++-- nscd/nscd_gethst_r.c | 10 +++---- nscd/nscd_getpw_r.c | 6 ++-- nscd/nscd_getserv_r.c | 7 ++--- nscd/nscd_initgroups.c | 5 ++-- nscd/nscd_netgroup.c | 11 +++---- nscd/nscd_proto.h | 21 +++++++++---- nscd/nscd_use_database.c | 57 ++++++++++++++++++++++++++++++++++++ nscd/tst-nscd_use_database.c | 53 +++++++++++++++++++++++++++++++++ nss/getXXbyYY_r.c | 10 ++----- nss/getaddrinfo.c | 12 ++++---- nss/getnetgrent_r.c | 14 ++------- nss/initgroups.c | 9 +----- nss/nss_database.c | 5 +++- nss/nss_module.c | 7 ++--- nss/nsswitch.c | 5 ---- nss/nsswitch.h | 2 -- 19 files changed, 166 insertions(+), 90 deletions(-) create mode 100644 nscd/nscd_use_database.c create mode 100644 nscd/tst-nscd_use_database.c diff --git a/nscd/Makefile b/nscd/Makefile index b6d7bd1cb5..f54b3bcbfc 100644 --- a/nscd/Makefile +++ b/nscd/Makefile @@ -24,8 +24,12 @@ include ../Makeconfig ifneq ($(use-nscd),no) routines := nscd_getpw_r nscd_getgr_r nscd_gethst_r nscd_getai \ - nscd_initgroups nscd_getserv_r nscd_netgroup + nscd_initgroups nscd_getserv_r nscd_netgroup nscd_use_database aux := nscd_helper + +tests-internal += \ + tst-nscd_use_database \ + # tests endif # To find xmalloc.c diff --git a/nscd/nscd_getai.c b/nscd/nscd_getai.c index 465e728fe4..b381a06695 100644 --- a/nscd/nscd_getai.c +++ b/nscd/nscd_getai.c @@ -27,10 +27,6 @@ #include "nscd_proto.h" -/* Define in nscd_gethst_r.c. */ -extern int __nss_not_use_nscd_hosts; - - /* We use the mapping from nscd_gethst. */ libc_locked_map_ptr (extern, __hst_map_handle) attribute_hidden; @@ -47,7 +43,7 @@ __nscd_getai (const char *key, struct nscd_ai_result **result, int *h_errnop) __nss_have_localdomain = getenv ("LOCALDOMAIN") != NULL ? 1 : -1; if (__nss_have_localdomain > 0) { - __nss_not_use_nscd_hosts = 1; + __nscd_defer_database (NSS_DBSIDX_hosts); return -1; } } @@ -98,7 +94,7 @@ __nscd_getai (const char *key, struct nscd_ai_result **result, int *h_errnop) if (sock == -1) { /* nscd not running or wrong version. */ - __nss_not_use_nscd_hosts = 1; + __nscd_defer_database (NSS_DBSIDX_hosts); goto out; } } @@ -173,7 +169,7 @@ __nscd_getai (const char *key, struct nscd_ai_result **result, int *h_errnop) if (__glibc_unlikely (ai_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_hosts = 1; + __nscd_defer_database (NSS_DBSIDX_hosts); goto out_close; } diff --git a/nscd/nscd_getgr_r.c b/nscd/nscd_getgr_r.c index 39b9fec2f1..173fadbd97 100644 --- a/nscd/nscd_getgr_r.c +++ b/nscd/nscd_getgr_r.c @@ -35,8 +35,6 @@ #include "nscd-client.h" #include "nscd_proto.h" -int __nss_not_use_nscd_group; - static int nscd_getgr_r (const char *key, size_t keylen, request_type type, struct group *resultbuf, char *buffer, size_t buflen, struct group **result); @@ -135,7 +133,7 @@ nscd_getgr_r (const char *key, size_t keylen, request_type type, sizeof (gr_resp)); if (sock == -1) { - __nss_not_use_nscd_group = 1; + __nscd_defer_database (NSS_DBSIDX_group); goto out; } } @@ -146,7 +144,7 @@ nscd_getgr_r (const char *key, size_t keylen, request_type type, if (__glibc_unlikely (gr_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_group = 1; + __nscd_defer_database (NSS_DBSIDX_group); goto out_close; } diff --git a/nscd/nscd_gethst_r.c b/nscd/nscd_gethst_r.c index 5f3eab2f4d..c4fd0906a2 100644 --- a/nscd/nscd_gethst_r.c +++ b/nscd/nscd_gethst_r.c @@ -26,8 +26,6 @@ #include "nscd-client.h" #include "nscd_proto.h" -int __nss_not_use_nscd_hosts; - static int nscd_gethst_r (const char *key, size_t keylen, request_type type, struct hostent *resultbuf, char *buffer, size_t buflen, struct hostent **result, @@ -97,7 +95,7 @@ uint32_t __nscd_get_nl_timestamp (void) { uint32_t retval; - if (__nss_not_use_nscd_hosts != 0) + if (!__nscd_use_database (NSS_DBSIDX_hosts)) return 0; /* __nscd_get_mapping can change hst_map_handle.mapped to NO_MAPPING. @@ -141,7 +139,7 @@ nscd_gethst_r (const char *key, size_t keylen, request_type type, __nss_have_localdomain = getenv ("LOCALDOMAIN") != NULL ? 1 : -1; if (__nss_have_localdomain > 0) { - __nss_not_use_nscd_hosts = 1; + __nscd_defer_database (NSS_DBSIDX_hosts); return -1; } } @@ -217,7 +215,7 @@ nscd_gethst_r (const char *key, size_t keylen, request_type type, sizeof (hst_resp)); if (sock == -1) { - __nss_not_use_nscd_hosts = 1; + __nscd_defer_database (NSS_DBSIDX_hosts); goto out; } } @@ -228,7 +226,7 @@ nscd_gethst_r (const char *key, size_t keylen, request_type type, if (__glibc_unlikely (hst_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_hosts = 1; + __nscd_defer_database (NSS_DBSIDX_hosts); goto out_close; } diff --git a/nscd/nscd_getpw_r.c b/nscd/nscd_getpw_r.c index 094770f536..cf02a51100 100644 --- a/nscd/nscd_getpw_r.c +++ b/nscd/nscd_getpw_r.c @@ -33,8 +33,6 @@ #include "nscd-client.h" #include "nscd_proto.h" -int __nss_not_use_nscd_passwd; - static int nscd_getpw_r (const char *key, size_t keylen, request_type type, struct passwd *resultbuf, char *buffer, size_t buflen, struct passwd **result); @@ -124,7 +122,7 @@ nscd_getpw_r (const char *key, size_t keylen, request_type type, sizeof (pw_resp)); if (sock == -1) { - __nss_not_use_nscd_passwd = 1; + __nscd_defer_database (NSS_DBSIDX_passwd); goto out; } } @@ -135,7 +133,7 @@ nscd_getpw_r (const char *key, size_t keylen, request_type type, if (__glibc_unlikely (pw_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_passwd = 1; + __nscd_defer_database (NSS_DBSIDX_passwd); goto out_close; } diff --git a/nscd/nscd_getserv_r.c b/nscd/nscd_getserv_r.c index b23afaf8a1..f50dbe574d 100644 --- a/nscd/nscd_getserv_r.c +++ b/nscd/nscd_getserv_r.c @@ -26,9 +26,6 @@ #include "nscd_proto.h" -int __nss_not_use_nscd_services; - - static int nscd_getserv_r (const char *crit, size_t critlen, const char *proto, request_type type, struct servent *resultbuf, char *buf, size_t buflen, struct servent **result); @@ -179,7 +176,7 @@ nscd_getserv_r (const char *crit, size_t critlen, const char *proto, sizeof (serv_resp)); if (sock == -1) { - __nss_not_use_nscd_services = 1; + __nscd_defer_database (NSS_DBSIDX_services); goto out; } } @@ -190,7 +187,7 @@ nscd_getserv_r (const char *crit, size_t critlen, const char *proto, if (__glibc_unlikely (serv_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_services = 1; + __nscd_defer_database (NSS_DBSIDX_services); goto out_close; } diff --git a/nscd/nscd_initgroups.c b/nscd/nscd_initgroups.c index 48bf597d1c..e0aae79089 100644 --- a/nscd/nscd_initgroups.c +++ b/nscd/nscd_initgroups.c @@ -16,6 +16,7 @@ . */ #include +#include #include #include #include @@ -83,7 +84,7 @@ __nscd_getgrouplist (const char *user, gid_t group, long int *size, if (sock == -1) { /* nscd not running or wrong version. */ - __nss_not_use_nscd_group = 1; + atomic_store_relaxed (&__nscd_skip_counters[NSS_DBSIDX_group], 1); goto out; } } @@ -132,7 +133,7 @@ __nscd_getgrouplist (const char *user, gid_t group, long int *size, if (__glibc_unlikely (initgr_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_group = 1; + atomic_store_relaxed (&__nscd_skip_counters[NSS_DBSIDX_group], 1); goto out_close; } diff --git a/nscd/nscd_netgroup.c b/nscd/nscd_netgroup.c index dd8f9da49b..2ea76c8722 100644 --- a/nscd/nscd_netgroup.c +++ b/nscd/nscd_netgroup.c @@ -24,9 +24,6 @@ #include "nscd-client.h" #include "nscd_proto.h" -int __nss_not_use_nscd_netgroup; - - libc_locked_map_ptr (static, map_handle); /* Note that we only free the structure if necessary. The memory mapping is not removed since it is not visible to the malloc @@ -87,7 +84,7 @@ __nscd_setnetgrent (const char *group, struct __netgrent *datap) if (sock == -1) { /* nscd not running or wrong version. */ - __nss_not_use_nscd_netgroup = 1; + __nscd_defer_database (NSS_DBSIDX_netgroup); goto out; } } @@ -127,7 +124,7 @@ __nscd_setnetgrent (const char *group, struct __netgrent *datap) if (__glibc_unlikely (netgroup_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_netgroup = 1; + __nscd_defer_database (NSS_DBSIDX_netgroup); goto out_close; } @@ -239,7 +236,7 @@ __nscd_innetgr (const char *netgroup, const char *host, const char *user, if (sock == -1) { /* nscd not running or wrong version. */ - __nss_not_use_nscd_netgroup = 1; + __nscd_defer_database (NSS_DBSIDX_netgroup); goto out; } @@ -251,7 +248,7 @@ __nscd_innetgr (const char *netgroup, const char *host, const char *user, if (__glibc_unlikely (innetgroup_resp.found == -1)) { /* The daemon does not cache this database. */ - __nss_not_use_nscd_netgroup = 1; + __nscd_defer_database (NSS_DBSIDX_netgroup); goto out_close; } diff --git a/nscd/nscd_proto.h b/nscd/nscd_proto.h index d188699fbd..3b635620dc 100644 --- a/nscd/nscd_proto.h +++ b/nscd/nscd_proto.h @@ -28,13 +28,22 @@ /* Type needed in the interfaces. */ struct nscd_ai_result; +/* This is set to 1 by nscd client functions to request not using nscd + for a bit. The main NSS code increments them until NSS_NSCD_RETRY + is reached, at which point nscd is attempted again. If the counter + is 0 (the default), use of nscd is attempted. If it is -1, nscd is + never used for this database. */ +extern int __nscd_skip_counters[NSS_DBSIDX_max] attribute_hidden; -/* Variables for communication between NSCD handler functions and NSS. */ -extern int __nss_not_use_nscd_passwd attribute_hidden; -extern int __nss_not_use_nscd_group attribute_hidden; -extern int __nss_not_use_nscd_hosts attribute_hidden; -extern int __nss_not_use_nscd_services attribute_hidden; -extern int __nss_not_use_nscd_netgroup attribute_hidden; +/* Update the internal per-database counters if necessary and return + true if nscd should be used for DATABASE for the next lookup. */ +_Bool __nscd_use_database (int database) attribute_hidden; + +/* Stop using DATABASE until NSS_NSCD_RETRY lookups have been performed. */ +void __nscd_defer_database (int database) attribute_hidden; + +/* Completely stop using DATABASE. */ +void __nscd_disable_database (int database) attribute_hidden; extern int __nscd_getpwnam_r (const char *name, struct passwd *resultbuf, char *buffer, size_t buflen, diff --git a/nscd/nscd_use_database.c b/nscd/nscd_use_database.c new file mode 100644 index 0000000000..2d6e48e32d --- /dev/null +++ b/nscd/nscd_use_database.c @@ -0,0 +1,57 @@ +/* Retry counter implementation for nscd. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +int __nscd_skip_counters[NSS_DBSIDX_max]; + +bool +__nscd_use_database (int database) +{ + int counter; + while (true) + { + counter = atomic_load_relaxed (&__nscd_skip_counters[database]); + if (counter <= 0) + break; + else if (counter > 0) + { + int old_counter = counter; + ++counter; + if (counter > NSS_NSCD_RETRY) + counter = 0; + if (atomic_compare_exchange_weak_relaxed + (&__nscd_skip_counters[database], &old_counter, counter)) + break; + } + } + return counter == 0; +} + +void +__nscd_defer_database (int database) +{ + atomic_store_relaxed (&__nscd_skip_counters[database], 1); +} + +void +__nscd_disable_database (int database) +{ + atomic_store_relaxed (&__nscd_skip_counters[database], -1); +} diff --git a/nscd/tst-nscd_use_database.c b/nscd/tst-nscd_use_database.c new file mode 100644 index 0000000000..3e5d1169c5 --- /dev/null +++ b/nscd/tst-nscd_use_database.c @@ -0,0 +1,53 @@ +/* Test the __nscd_use_database function. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +static int +do_test (void) +{ + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_hosts], 0); + TEST_VERIFY (__nscd_use_database (NSS_DBSIDX_hosts)); + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_hosts], 0); + TEST_VERIFY (__nscd_use_database (NSS_DBSIDX_hosts)); + + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_group], 0); + __nscd_skip_counters[NSS_DBSIDX_group] = -1; + TEST_VERIFY (!__nscd_use_database (NSS_DBSIDX_group)); + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_group], -1); + + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_passwd], 0); + __nscd_defer_database (NSS_DBSIDX_passwd); + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_passwd], 1); + TEST_VERIFY (!__nscd_use_database (NSS_DBSIDX_passwd)); + for (int i = 2; i < NSS_NSCD_RETRY; ++i) + { + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_passwd], i); + TEST_VERIFY (!__nscd_use_database (NSS_DBSIDX_passwd)); + } + TEST_VERIFY (__nscd_use_database (NSS_DBSIDX_hosts)); + TEST_COMPARE (__nscd_skip_counters[NSS_DBSIDX_hosts], 0); + + return 0; +} + +#include + +/* Recompile due to use of internal symbol. */ +#include "nscd_use_database.c" diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c index 9f7188ab41..37836fd040 100644 --- a/nss/getXXbyYY_r.c +++ b/nss/getXXbyYY_r.c @@ -78,9 +78,7 @@ # define NSCD_NAME ADD_NSCD (REENTRANT_NAME) # define ADD_NSCD(name) ADD_NSCD1 (name) # define ADD_NSCD1(name) __nscd_##name -# define NOT_USENSCD_NAME ADD_NOT_NSCDUSE (DATABASE_NAME) -# define ADD_NOT_NSCDUSE(name) ADD_NOT_NSCDUSE1 (name) -# define ADD_NOT_NSCDUSE1(name) __nss_not_use_nscd_##name +# define NSS_DATABASE_INDEX CONCAT2 (NSS_DBSIDX_, DATABASE_NAME) # define CONCAT2(arg1, arg2) CONCAT2_2 (arg1, arg2) # define CONCAT2_2(arg1, arg2) arg1##arg2 #endif @@ -235,11 +233,7 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer, #endif #ifdef USE_NSCD - if (NOT_USENSCD_NAME > 0 && ++NOT_USENSCD_NAME > NSS_NSCD_RETRY) - NOT_USENSCD_NAME = 0; - - if (!NOT_USENSCD_NAME - && !__nss_database_custom[CONCAT2 (NSS_DBSIDX_, DATABASE_NAME)]) + if (__nscd_use_database(NSS_DATABASE_INDEX)) { nscd_status = NSCD_NAME (ADD_VARIABLES, resbuf, buffer, buflen, result H_ERRNO_VAR); diff --git a/nss/getaddrinfo.c b/nss/getaddrinfo.c index 6726ace6fd..8605a980bb 100644 --- a/nss/getaddrinfo.c +++ b/nss/getaddrinfo.c @@ -81,7 +81,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#ifdef USE_NSCD +# include +#endif #include #include @@ -485,13 +487,9 @@ static int get_nscd_addresses (const char *name, const struct addrinfo *req, struct gaih_result *res) { - if (__nss_not_use_nscd_hosts > 0 - && ++__nss_not_use_nscd_hosts > NSS_NSCD_RETRY) - __nss_not_use_nscd_hosts = 0; - res->at = NULL; - if (__nss_not_use_nscd_hosts || __nss_database_custom[NSS_DBSIDX_hosts]) + if (!__nscd_use_database (NSS_DBSIDX_hosts)) return 0; /* Try to use nscd. */ @@ -503,7 +501,7 @@ get_nscd_addresses (const char *name, const struct addrinfo *req, /* The database contains a negative entry. */ if (err == 0) return -EAI_NONAME; - if (__nss_not_use_nscd_hosts == 0) + if (atomic_load_relaxed (&__nscd_skip_counters[NSS_DBSIDX_hosts]) == 0) { if (h_errno == NETDB_INTERNAL && errno == ENOMEM) return -EAI_MEMORY; diff --git a/nss/getnetgrent_r.c b/nss/getnetgrent_r.c index df16cfe150..11d3e8b562 100644 --- a/nss/getnetgrent_r.c +++ b/nss/getnetgrent_r.c @@ -150,12 +150,7 @@ static int nscd_setnetgrent (const char *group) { #ifdef USE_NSCD - if (__nss_not_use_nscd_netgroup > 0 - && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY) - __nss_not_use_nscd_netgroup = 0; - - if (!__nss_not_use_nscd_netgroup - && !__nss_database_custom[NSS_DBSIDX_netgroup]) + if (__nscd_use_database (NSS_DBSIDX_netgroup)) return __nscd_setnetgrent (group, &dataset); #endif return -1; @@ -357,12 +352,7 @@ innetgr (const char *netgroup, const char *host, const char *user, const char *domain) { #ifdef USE_NSCD - if (__nss_not_use_nscd_netgroup > 0 - && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY) - __nss_not_use_nscd_netgroup = 0; - - if (!__nss_not_use_nscd_netgroup - && !__nss_database_custom[NSS_DBSIDX_netgroup]) + if (__nscd_use_database (NSS_DBSIDX_netgroup)) { int result = __nscd_innetgr (netgroup, host, user, domain); if (result >= 0) diff --git a/nss/initgroups.c b/nss/initgroups.c index 4a652ff782..e594622207 100644 --- a/nss/initgroups.c +++ b/nss/initgroups.c @@ -47,18 +47,11 @@ internal_getgrouplist (const char *user, gid_t group, long int *size, gid_t **groupsp, long int limit) { #ifdef USE_NSCD - if (__nss_not_use_nscd_group > 0 - && ++__nss_not_use_nscd_group > NSS_NSCD_RETRY) - __nss_not_use_nscd_group = 0; - if (!__nss_not_use_nscd_group - && !__nss_database_custom[NSS_DBSIDX_group]) + if (__nscd_use_database (NSS_DBSIDX_group)) { int n = __nscd_getgrouplist (user, group, size, groupsp, limit); if (n >= 0) return n; - - /* nscd is not usable. */ - __nss_not_use_nscd_group = 1; } #endif diff --git a/nss/nss_database.c b/nss/nss_database.c index bed353c59b..66f91421b7 100644 --- a/nss/nss_database.c +++ b/nss/nss_database.c @@ -28,6 +28,9 @@ #include #include #include +#ifdef USE_NSCD +# include +#endif struct nss_database_state { @@ -254,7 +257,7 @@ __nss_configure_lookup (const char *dbname, const char *service_line) local->data.services[db] = result; #ifdef USE_NSCD - __nss_database_custom[db] = true; + __nscd_disable_database (db); #endif return 0; diff --git a/nss/nss_module.c b/nss/nss_module.c index ac94e4d3f0..7289d41f79 100644 --- a/nss/nss_module.c +++ b/nss/nss_module.c @@ -395,11 +395,8 @@ __nss_disable_nscd (void (*cb) (size_t, struct traced_file *)) cb1 (netgrdb, &netgr_traced_file.file); /* Disable all uses of NSCD. */ - __nss_not_use_nscd_passwd = -1; - __nss_not_use_nscd_group = -1; - __nss_not_use_nscd_hosts = -1; - __nss_not_use_nscd_services = -1; - __nss_not_use_nscd_netgroup = -1; + for (int i = 0; i < NSS_DBSIDX_max; ++i) + __nscd_disable_database (i); } #endif diff --git a/nss/nsswitch.c b/nss/nsswitch.c index b23bb4c2c1..4d567b0e22 100644 --- a/nss/nsswitch.c +++ b/nss/nsswitch.c @@ -42,11 +42,6 @@ #include #include -#ifdef USE_NSCD -/* Flags whether custom rules for database is set. */ -bool __nss_database_custom[NSS_DBSIDX_max]; -#endif - /*__libc_lock_define_initialized (static, lock)*/ /* -1 == not found diff --git a/nss/nsswitch.h b/nss/nsswitch.h index 9799627aa2..6514a3d63d 100644 --- a/nss/nsswitch.h +++ b/nss/nsswitch.h @@ -73,8 +73,6 @@ enum NSS_DBSIDX_max }; -/* Flags whether custom rules for database is set. */ -extern bool __nss_database_custom[NSS_DBSIDX_max] attribute_hidden; #endif /* Warning for NSS functions, which don't require dlopen if glibc From patchwork Mon Nov 24 19:47:47 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125189 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A3A3A3858422 for ; Mon, 24 Nov 2025 19:50:45 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A3A3A3858422 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q2agF+H3 X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 50AE73858417 for ; Mon, 24 Nov 2025 19:47:53 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 50AE73858417 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 50AE73858417 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013673; cv=none; b=R/5NFOdFvjWl7T5CKGNDvR8XMZT98Py2Urq7ZjxKfHBdaKrJ4fQIUiIZ4vLDKXWDK1ynqI39N1K7W/t4tHkaJO8aZDkoqXESYTINa6+JFVKpi+AK50vc6b4ohfQXM3fk1C2hhy8a+jpqLjqiX7Qpuuyj+Zk9ngvLknHGjMbuTQ8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013673; c=relaxed/simple; bh=64y8zgE08S0uemK+zXMFwbXg0CRgwi/ON1lEChKijSA=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=iDJC4XyOUi0SdAemIvGvlq4meWQSO8tElbkJDbOplJWOAUOR11xeRKYdMIF3ZEL7ybOH2bj4Z877uWaRvogiTJTFmwSyLTdDxMugZGRYsJ2uYlrGjdj3qpyMclK3kusPdBwgIpYKzFgmJ99Xs6i/zcZ7NqVlJiCIvT6NImuG2kg= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 50AE73858417 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013673; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=FvV1+1OEsrFtwXVzbHdT4BK4zPiyB8BxsqyqKGMj1mk=; b=Q2agF+H3tkVZ/4lKae7IbpRZIlCU7J+CIPialb6EylBOdn0w+bzhB+QczuI6296mmgczm2 CkEEIUBqrtekQyXCRuOC1MVSrh9vGk8ytGbsrxXG2ogbC3DNYz9SgUTyoXLIHEhIAfN8RM Izs2WTJVqblbJqeScjvZeRUzl0cuJQ8= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-376--BFaFmFLN2y989KTU6a0TQ-1; Mon, 24 Nov 2025 14:47:51 -0500 X-MC-Unique: -BFaFmFLN2y989KTU6a0TQ-1 X-Mimecast-MFC-AGG-ID: -BFaFmFLN2y989KTU6a0TQ_1764013670 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 91CFE19560A3 for ; Mon, 24 Nov 2025 19:47:50 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id BDDF419560B2 for ; Mon, 24 Nov 2025 19:47:49 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 2/7] nss: Add negative lookup test In-Reply-To: Message-ID: <0ac4b684d13adb8edc5feec39bf50f57d1df4cc4.1764012721.git.fweimer@redhat.com> References: X-From-Line: 0ac4b684d13adb8edc5feec39bf50f57d1df4cc4 Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:47:47 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: JgtPXTqeWNlIOy-jviLTBCATegkwI_BXX8v_TcUjRK0_1764013670 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, PROLO_LEO1, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org --- nss/Makefile | 3 + nss/tst-nss-does-not-exist.cc | 210 ++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 nss/tst-nss-does-not-exist.cc diff --git a/nss/Makefile b/nss/Makefile index 0586381877..07556a5958 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -324,6 +324,7 @@ tests := \ tst-gethnm \ tst-getpw \ tst-gshadow \ + tst-nss-does-not-exist \ tst-nss-getpwent \ tst-nss-hash \ tst-nss-test1 \ @@ -536,3 +537,5 @@ LDFLAGS-tst-nss-test4 = -Wl,--disable-new-dtags LDFLAGS-tst-nss-test5 = -Wl,--disable-new-dtags LDFLAGS-tst-nss-test_errno = -Wl,--disable-new-dtags LDFLAGS-tst-nss-test_gai_hv2_canonname = -Wl,--disable-new-dtags + +LDLIBS-tst-nss-does-not-exist = -lstdc++ diff --git a/nss/tst-nss-does-not-exist.cc b/nss/tst-nss-does-not-exist.cc new file mode 100644 index 0000000000..0c84144d0c --- /dev/null +++ b/nss/tst-nss-does-not-exist.cc @@ -0,0 +1,210 @@ +/* Verify that lookup failures have the expected error behavior. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test case triggers lookup failures for most NSS functions + (both allocated and *_r variants). It is written in C++ so that + the required types can be inferred from the lookup functions + themselves. */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static const char non_existing_name[] + = "this-name-does-not-exist-in-the-system"; + +/* True if *_r lookup functions for type T use an extra h_errno + argument. */ +template constexpr bool uses_h_errno = + std::is_same_v || std::is_same_v ; + +/* Call a *_r NSS function that produces an NSS lookup of result T + repeatedly until it does not return ERANGE. The final return value + is written to the ret member, and the pointer to the result + structure to the result member. The result can be nullptr for a + negative lookup result. */ +template +struct erange_wrapper +{ + int ret; + T *result; + + template + erange_wrapper (Impl impl, Args&&... args) + { + buffer.resize (32); + result = (T *) -1; + while (true) + { + h_errno = 0; + int herrno = 0; + if constexpr (uses_h_errno ) + ret = impl (std::forward(args)..., + &storage, buffer.data (), buffer.size (), &result, + &herrno); + else + { + /* No h_errno pointer argument. Pretend that failures + are always due to NETDB_INTERNAL. */ + ret = impl (std::forward(args)..., + &storage, buffer.data (), buffer.size (), &result); + herrno = NETDB_INTERNAL; + } + if (herrno == NETDB_INTERNAL && ret == ERANGE) + { + buffer.resize (buffer.size () * 2); + TEST_VERIFY (buffer.size () > 0); + } + else + break; + } + } + + /* Verify that ret and result denote a negative lookup result. */ + void expected_negative () + { + TEST_COMPARE (ret, 0); + TEST_VERIFY (result == nullptr); + } + +private: + T storage; + std::vector buffer; +}; + +/* Verify that looking up non_existing_name fails for the non-_r and _r + function variants. */ +template +static void +check_missing_name (Lookup lookup, LookupR lookup_r, Args&&... args) +{ + verbose_printf ("info: name lookup: %s\n", + strchr (__PRETTY_FUNCTION__, '[')); + errno = 0; + auto r = lookup (non_existing_name, std::forward(args)...); + TEST_VERIFY (r == nullptr); + TEST_COMPARE (errno, 0); + using nss_type = typename std::remove_reference ::type; + erange_wrapper (lookup_r, non_existing_name, + std::forward(args)...).expected_negative (); +} + +/* Find an ID that results in a lookup failure using the non-_r function, + and then verify that it also fails with the _r function. ID_MEMBER + is the pointer to the struct member that contains the looked-up ID. */ +template +static void +find_missing_id (const char *type_name, Lookup lookup, LookupR lookup_r, + Member id_member, Args&&... args) +{ + verbose_printf ("info: ID lookup for %s: %s\n", + type_name, strchr (__PRETTY_FUNCTION__, '[')); + using nss_type = typename std::remove_reference + (args)...))>::type; + using id_type = decltype(nss_type{}.*id_member); + for (id_type i = 0; i < 65536; ++i) + { + verbose_printf ("info: trying %lld\n", (long long int) i); + errno = 0; + nss_type *r = lookup (i, std::forward(args)...); + if (r == nullptr) + { + TEST_COMPARE (errno, 0); + printf ("info: first missing %s: %lld\n", type_name, + (long long int) i); + + erange_wrapper (lookup_r, i, std::forward(args)...) + .expected_negative (); + return; + } + TEST_COMPARE (r->*id_member, i); + } + printf ("warning: could not find an undefined ID for %s\n", type_name); +} + +static void +checks (void *closure) +{ + check_missing_name (getpwnam, getpwnam_r); + find_missing_id ("passwd", getpwuid, getpwuid_r, &passwd::pw_uid); + + check_missing_name (getgrnam, getgrnam_r); + find_missing_id ("group", getgrgid, getgrgid_r, &group::gr_gid); + + check_missing_name (getprotobyname, getprotobyname_r); + find_missing_id ("protoent", getprotobynumber, getprotobynumber_r, + &protoent::p_proto); + + check_missing_name (getservbyname, getservbyname_r, "tcp"); + find_missing_id ("servent", getservbyport, getservbyport_r, + &servent::s_port, "tcp"); + + check_missing_name (getaliasbyname, getaliasbyname_r); + + check_missing_name (getrpcbyname, getrpcbyname_r); + find_missing_id ("rpcent", getrpcbynumber, getrpcbynumber_r, + &rpcent::r_number); + + check_missing_name (gethostbyname, gethostbyname_r); + check_missing_name (gethostbyname2, gethostbyname2_r, AF_INET); + + check_missing_name (getnetbyname, getnetbyname_r); + + /* Exceptional case: no buffer argument, no ERANGE protocol. */ + { + errno = 0; + TEST_VERIFY (ether_aton (non_existing_name) == nullptr); + TEST_COMPARE (errno, 0); + struct ether_addr addr; + TEST_VERIFY (ether_aton_r (non_existing_name, &addr) == nullptr); + TEST_COMPARE (errno, 0); + } +} + +static int +do_test (void) +{ + /* First test the system default. */ + puts ("info: testing system default"); + support_isolate_in_subprocess (checks, nullptr); + + /* Then test the files module specifically. */ + puts ("info: testing files module"); +#define DEFINE_DATABASE(db) __nss_configure_lookup (#db, "files"); +#include "databases.def" +#undef DEFINE_DATABASE + support_isolate_in_subprocess (checks, nullptr); + + return 0; +} + +#include From patchwork Mon Nov 24 19:47:54 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125190 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 23D503858413 for ; Mon, 24 Nov 2025 19:51:01 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 23D503858413 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=hwIgXB4Q X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id C544A385841C for ; Mon, 24 Nov 2025 19:48:00 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C544A385841C Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org C544A385841C Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013680; cv=none; b=Idzz6AtBjs7DsI1x04ZeBbKFGz9r293uRqCj3VQHpt1QvBfvMMMcbozhI8SVDx0Jc29ghdx+CFJJeJNxQyn0DSYKwvd7XmVtr03wNo2R8bY5bXM818+2cZTIu+wKo4A4tQDMBjv5c27FwuTKIbk+Wn7JQ6TZvDs2C/Lkv69MzLQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013680; c=relaxed/simple; bh=Y+TqRWEFaI71vPiWp5LU1PRUQ6OlU5AHI2Tvz+l5klE=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=ib6yKm9PwuHxosmIEb+agBDrhtemcZyj0SEae2XPAhdCNRVlLvrhZ3/NjTH7pyYmTtVWaLak9mlwxh3F52DYedKzeXthJTRIdSgrb3OUnFTTotrBDagK5KG5AqRPY0kQcMeqT0Mv/43l7SJ0kLFANNpi/9nxJsfAvlE9kmRsZww= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org C544A385841C DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013680; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=lW+eu4CNf3ViSgvGAcHDebp4sJJFk/vUWx509h43VKA=; b=hwIgXB4QOjYgujeIEE7sC9WvmOQxdhZcD7XfF1cuAD2BoCzg784jFxgSBDVjpk0fg4vKpu 2A9zoJtrlSkLtUXpnfSPNPGdJ/kzXl4n2m3bgYAJQZOcNVhe9EoxQ8A+1jvkK1ljBLNVYJ Jig/UGZ45qJ/oRqslJ4DrqXGB7LgMsA= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-382-2QH0Y-vFPoW1-VN1Osy3pQ-1; Mon, 24 Nov 2025 14:47:59 -0500 X-MC-Unique: 2QH0Y-vFPoW1-VN1Osy3pQ-1 X-Mimecast-MFC-AGG-ID: 2QH0Y-vFPoW1-VN1Osy3pQ_1764013678 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 37B181800342 for ; Mon, 24 Nov 2025 19:47:58 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 456F530044DC for ; Mon, 24 Nov 2025 19:47:56 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 3/7] nss: Add __nss_generic_copy and __nss_generic_dup functions In-Reply-To: Message-ID: References: X-From-Line: b7ba519b3848c179d3e67335a93f150c5eee4fd4 Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:47:54 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: zqbvrAt9r0yxa-glE7OhJLHy_LYO-3tj8qYQqYwV1h8_1764013678 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org So far for struct group only. These functions will be used internally in the type-generic NSS implementation, to preserve intermediate results and to produce the final *_r result. --- nss/Makefile | 10 ++- nss/nss-lookups.def | 26 +++++++ nss/nss_generic.h | 61 +++++++++++++++++ nss/nss_generic_copy.c | 90 +++++++++++++++++++++++++ nss/nss_generic_dup.c | 88 ++++++++++++++++++++++++ nss/tst-nss_generic_copy.c | 131 ++++++++++++++++++++++++++++++++++++ nss/tst-nss_generic_dup.c | 134 +++++++++++++++++++++++++++++++++++++ 7 files changed, 539 insertions(+), 1 deletion(-) create mode 100644 nss/nss-lookups.def create mode 100644 nss/nss_generic.h create mode 100644 nss/nss_generic_copy.c create mode 100644 nss/nss_generic_dup.c create mode 100644 nss/tst-nss_generic_copy.c create mode 100644 nss/tst-nss_generic_dup.c diff --git a/nss/Makefile b/nss/Makefile index 07556a5958..745a328d6e 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -45,6 +45,8 @@ routines = \ nss_files_data \ nss_files_fopen \ nss_files_functions \ + nss_generic_copy \ + nss_generic_dup \ nss_hash \ nss_module \ nss_parse_line_result \ @@ -302,10 +304,16 @@ makedb-modules = xmalloc hash-string others-extras = $(makedb-modules) extra-objs += $(makedb-modules:=.o) -tests-static = tst-field +tests-static = \ + tst-field \ + tst-nss_generic_copy \ + tst-nss_generic_dup \ + # tests-static tests-internal := \ tst-field \ + tst-nss_generic_copy \ + tst-nss_generic_dup \ tst-rfc3484 \ tst-rfc3484-2 \ tst-rfc3484-3 \ diff --git a/nss/nss-lookups.def b/nss/nss-lookups.def new file mode 100644 index 0000000000..012d67fbae --- /dev/null +++ b/nss/nss-lookups.def @@ -0,0 +1,26 @@ +/* Lookup type definitions for NSS. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +DEFINE_LOOKUP (getgrgid, group, "getgrgid_r") +DEFINE_LOOKUP (getgrnam, group, "getgrnam_r") + +/* + Local Variables: + mode:C + End: + */ diff --git a/nss/nss_generic.h b/nss/nss_generic.h new file mode 100644 index 0000000000..820169ca12 --- /dev/null +++ b/nss/nss_generic.h @@ -0,0 +1,61 @@ +/* Type- and query-agonstic NSS functionality. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* The functions declared in this file use void * instead of concrete + pointer types such as struct group and NSS service module function + pointers. They can be combined to implement different high-level, + type-safe functions. */ + + +#ifndef NSS_GENERIC_H +#define NSS_GENERIC_H + +#include + +enum nss_lookup_type + { +#define DEFINE_LOOKUP(name, database, function_1) nss_lookup_##name, +#include "nss-lookups.def" +#undef DEFINE_LOOKUP + }; + +/* Define the nss_lookup_MAX constant. This is not part of enum + nss_lookup_type so that switch coverage warnings work. */ +enum + { +#define DEFINE_LOOKUP(name, database, function_1) HIDDEN_nss_lookup_##name, +#include "nss-lookups.def" +#undef DEFINE_LOOKUP + nss_lookup_MAX + }; + +/* Copy an NSS struct corresponding to LT into DEST, potentially using + additional storage of LENGTH bytes at BUFFER. If BUFFER is not + large enough, return ERANGE and set errno to ERANGE. Otherwise + return zero. */ +int __nss_generic_copy (enum nss_lookup_type lt, const void *source, + void *dest, char *buffer, size_t length) + attribute_hidden; + +/* Create a malloc-allocated copy of the NSS struct of type LT at + SOURCE and return a pointer to it. Return NULL on allocation + failure. */ +void *__nss_generic_dup (enum nss_lookup_type lt, const void *source) + attribute_hidden; + +#endif /* NSS_GENERIC */ diff --git a/nss/nss_generic_copy.c b/nss/nss_generic_copy.c new file mode 100644 index 0000000000..78945b79c3 --- /dev/null +++ b/nss/nss_generic_copy.c @@ -0,0 +1,90 @@ +/* Copy an NSS struct into a fixed-size destination buffer. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include + +static char * +safe_copy_string (struct alloc_buffer *buf, const char *source) +{ + if (source == NULL) + return NULL; + else + return alloc_buffer_copy_string (buf, source); +} + +static void +__nss_copy_grp (enum nss_lookup_type lt, const struct group *source, + struct group *result, struct alloc_buffer *buf) +{ + *result = *source; + + size_t member_count = 0; + if (source->gr_mem != 0) + { + while (source->gr_mem[member_count] != NULL) + ++member_count; + + /* Copy the array first, to minimize the alignment requirements. */ + result->gr_mem = alloc_buffer_alloc_array (buf, char *, member_count + 1); + if (result->gr_mem == NULL) + return; + + for (size_t i = 0; i < member_count; ++i) + result->gr_mem[i] + = alloc_buffer_copy_string (buf, source->gr_mem[i]); + + result->gr_mem[member_count] = NULL; + } + + result->gr_name = safe_copy_string (buf, source->gr_name); + result->gr_passwd = safe_copy_string (buf, source->gr_passwd); +} + +static void +__nss_do_copy (enum nss_lookup_type lt, const void *source, void *result, + struct alloc_buffer *buf) +{ + switch (lt) + { + case nss_lookup_getgrgid: + case nss_lookup_getgrnam: + return __nss_copy_grp (lt, source, result, buf); + } + __builtin_unreachable (); +} + +int +__nss_generic_copy (enum nss_lookup_type lt, const void *source, + void *result, char *buffer, size_t length) +{ + struct alloc_buffer buf = alloc_buffer_create (buffer, length); + + __nss_do_copy (lt, source, result, &buf); + + /* Check if any allocation failed. */ + if (alloc_buffer_has_failed (&buf)) + { + __set_errno (ERANGE); + return ERANGE; + } + + return 0; +} diff --git a/nss/nss_generic_dup.c b/nss/nss_generic_dup.c new file mode 100644 index 0000000000..44bf4cbb3e --- /dev/null +++ b/nss/nss_generic_dup.c @@ -0,0 +1,88 @@ +/* Duplicate struct group data with a single malloc allocation. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +static size_t +safe_strlen_null (const char *source) +{ + if (source == NULL) + return 0; + else + return strlen (source) + 1; +} + +static size_t +__nss_group_buffer_size (const struct group *source) +{ + size_t size = sizeof (*source); + size += safe_strlen_null (source->gr_name); + size += safe_strlen_null (source->gr_passwd); + + /* Assume that the array is allocated first, so that no + alignment is needed. */ + if (source->gr_mem != NULL) + { + for (size_t i = 0; source->gr_mem[i] != NULL; ++i) + size += sizeof (char *) + strlen (source->gr_mem[i]) + 1; + size += sizeof (char *); + } + + return size; +} + +static size_t +__nss_buffer_size (enum nss_lookup_type lt, const void *source) +{ + switch (lt) + { + case nss_lookup_getgrgid: + case nss_lookup_getgrnam: + return __nss_group_buffer_size (source); + } + + __builtin_unreachable (); +} + +void * +__nss_generic_dup (enum nss_lookup_type lt, const void *source) +{ + struct group *result; + char *buf; + char *end; + { + size_t size = __nss_buffer_size (lt, source); + result = malloc (size); + if (result == NULL) + return NULL; + buf = (char *) (result + 1); + end = (char *) result + size; + } + + /* Assert that the computed size was correct. */ + int ret __attribute__ ((unused)) + = __nss_generic_copy (lt, source, result, buf, end - buf); + assert (ret == 0); + + return result; +} diff --git a/nss/tst-nss_generic_copy.c b/nss/tst-nss_generic_copy.c new file mode 100644 index 0000000000..c1c8af3f25 --- /dev/null +++ b/nss/tst-nss_generic_copy.c @@ -0,0 +1,131 @@ +/* Test program for the __nss_generic_copy function. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test needs to be statically linked because it accesses the + hidden function __nss_generic_copy. */ + +#include +#include +#include +#include +#include +#include +#include + +static void +test_group (enum nss_lookup_type lt) +{ + char *members[4] = { (char *) "user1", (char *) "user2", (char *) "user3", }; + struct group source = + { + .gr_name = (char *) "group-name", + .gr_passwd = (char *) "password", + .gr_gid = 1000, + .gr_mem = members + }; + + /* Test with sufficient buffer size. */ + { + char buffer[1024]; + struct group result; + + errno = 23587; + TEST_COMPARE (__nss_generic_copy (lt, &source, &result, + buffer, sizeof (buffer)), + 0); + TEST_VERIFY (errno != 0); + + /* Verify the copied data */ + TEST_COMPARE_STRING (result.gr_name, "group-name"); + TEST_COMPARE_STRING (result.gr_passwd, "password"); + TEST_COMPARE (result.gr_gid, 1000); + TEST_COMPARE_STRING (result.gr_mem[0], "user1"); + TEST_COMPARE_STRING (result.gr_mem[1], "user2"); + TEST_COMPARE_STRING (result.gr_mem[2], "user3"); + TEST_COMPARE_STRING (result.gr_mem[3], NULL); + } + + /* Test with insufficient buffer size. */ + { + char buffer[10]; + struct group result; + + errno = 23587; + int ret = __nss_generic_copy (lt, &source, + &result, buffer, sizeof (buffer)); + TEST_COMPARE (ret, ERANGE); + TEST_COMPARE (errno, ERANGE); + } + + /* Test with NULL members. */ + { + source.gr_name = NULL; + source.gr_passwd = NULL; + source.gr_mem = NULL; + + char buffer[1024]; + struct group result; + memset (&result, 0xcc, sizeof (result)); + + errno = 23587; + TEST_COMPARE (__nss_generic_copy (lt, &source, &result, + buffer, sizeof (buffer)), + 0); + TEST_VERIFY (errno != 0); + TEST_COMPARE_STRING (result.gr_name, NULL); + TEST_COMPARE_STRING (result.gr_passwd, NULL); + TEST_COMPARE (result.gr_gid, 1000); + TEST_VERIFY (result.gr_mem == NULL); + } + + /* Test with empty strings. */ + { + char empty[] = ""; + char *list[] = { empty, NULL }; + source.gr_name = (char *) ""; + source.gr_passwd = (char *) ""; + source.gr_mem = list; + + char buffer[1024]; + struct group result; + + errno = 23587; + TEST_COMPARE (__nss_generic_copy (lt, &source, &result, + buffer, sizeof (buffer)), + 0); + TEST_VERIFY (errno != 0); + + /* Verify the copied data */ + TEST_COMPARE_STRING (result.gr_name, ""); + TEST_COMPARE_STRING (result.gr_passwd, ""); + TEST_COMPARE (result.gr_gid, 1000); + TEST_COMPARE_STRING (result.gr_mem[0], ""); + TEST_COMPARE_STRING (result.gr_mem[1], NULL); + } +} + +static int +do_test (void) +{ + test_group (nss_lookup_getgrgid); + test_group (nss_lookup_getgrnam); + + return 0; +} + +#include diff --git a/nss/tst-nss_generic_dup.c b/nss/tst-nss_generic_dup.c new file mode 100644 index 0000000000..aff5789433 --- /dev/null +++ b/nss/tst-nss_generic_dup.c @@ -0,0 +1,134 @@ +/* Test program for the __nss_generic_dup function. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test needs to be statically linked because it accesses the + hidden function __nss_generic_dup. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +check_pointer_in_allocation (void *allocation, void *ptr) +{ + TEST_VERIFY (ptr >= allocation); + TEST_VERIFY (ptr < (void *) ((char *) allocation + + malloc_usable_size (allocation))); +} + +static void +test_group (enum nss_lookup_type lt) +{ + char *members[4] = { (char *) "user1", (char *) "user2", + (char *) "long-user-3-name" }; + struct group source = + { + .gr_name = (char *) "group-name", + .gr_passwd = (char *) "password", + .gr_gid = 1000, + .gr_mem = members + }; + + /* Test with normal group data. */ + { + struct group *result = __nss_generic_dup (lt, &source); + TEST_VERIFY (result != NULL); + + /* Verify the copied data */ + TEST_COMPARE_STRING (result->gr_name, "group-name"); + TEST_COMPARE_STRING (result->gr_passwd, "password"); + TEST_COMPARE (result->gr_gid, 1000); + TEST_COMPARE_STRING (result->gr_mem[0], "user1"); + TEST_COMPARE_STRING (result->gr_mem[1], "user2"); + TEST_COMPARE_STRING (result->gr_mem[2], "long-user-3-name"); + TEST_VERIFY (result->gr_mem[3] == NULL); + + check_pointer_in_allocation (result, result->gr_name); + check_pointer_in_allocation (result, result->gr_passwd); + check_pointer_in_allocation (result, result->gr_mem); + for (int i = 0; result->gr_mem[i] != NULL; i++) + check_pointer_in_allocation (result, result->gr_mem[i]); + + free (result); + } + + /* Test with NULL members. */ + { + struct group source_null = + { + .gr_gid = 2000, + }; + + struct group *result = __nss_generic_dup (lt, &source_null); + TEST_VERIFY (result != NULL); + + TEST_VERIFY (result->gr_name == NULL); + TEST_VERIFY (result->gr_passwd == NULL); + TEST_COMPARE (result->gr_gid, 2000); + TEST_VERIFY (result->gr_mem == NULL); + + free (result); + } + + /* Test with empty strings. */ + { + char empty[] = ""; + char *empty_list[] = { empty, NULL }; + struct group source_empty = + { + .gr_name = (char *) "", + .gr_passwd = (char *) "", + .gr_gid = 3000, + .gr_mem = empty_list + }; + + struct group *result = __nss_generic_dup (lt, &source_empty); + TEST_VERIFY (result != NULL); + + TEST_COMPARE_STRING (result->gr_name, ""); + TEST_COMPARE_STRING (result->gr_passwd, ""); + TEST_COMPARE (result->gr_gid, 3000); + TEST_COMPARE_STRING (result->gr_mem[0], ""); + TEST_VERIFY (result->gr_mem[1] == NULL); + + check_pointer_in_allocation (result, result->gr_name); + check_pointer_in_allocation (result, result->gr_passwd); + check_pointer_in_allocation (result, result->gr_mem); + for (int i = 0; result->gr_mem[i] != NULL; i++) + check_pointer_in_allocation (result, result->gr_mem[i]); + + free (result); + } +} + +static int +do_test (void) +{ + test_group (nss_lookup_getgrgid); + test_group (nss_lookup_getgrnam); + + return 0; +} + +#include From patchwork Mon Nov 24 19:48:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125192 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 68FC73858D20 for ; Mon, 24 Nov 2025 19:54:27 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 68FC73858D20 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=VG0fzSUa X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 8BA04385841B for ; Mon, 24 Nov 2025 19:48:10 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8BA04385841B Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 8BA04385841B Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013695; cv=none; b=Ak1a/Tc0kO4yyGsYqPCNdEbF40bhGSKKIIWLloyMuEh8k+Ge+OHWcOy0UuPv+Fiqhby25nVklKkwoTcZtMOd3dQLBHIj11uuV3Q7mADoqRMn9aH/oUFNHJcJwSOX3NkFifPhmDb0tEvo7xLh++1/FQPgM5JNF4EjtjrPUIeOdBs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013695; c=relaxed/simple; bh=2T0m7ZZVJV1XYTcF9lY7uL7SqMpOqUI7prle/NH07ZM=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=Ik1RBbbf9TgbE1Kocba5vvNtpJ/vZhF4KvnnYvsgWo2+YSDPo/2I9oK++tHCSIBqKYAJcWvJSu6yc03ciULnTRGYxrnDzgk0BqznVXbnfsIilQ5h+08f6dpVwWEHf8/GTsWsj8iyGhV7tP3OE95N7cUAvOA6s8vQOtPhohzH7U8= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8BA04385841B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013690; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=KTUtbrwmDnq2qFQe/oq7yK7J3qOgX0TLy5pOu4ozdmA=; b=VG0fzSUa+uFtaQ/khfT2mTZdkc1oaBvDW8RTREvThB3yBKy+uK0ZYcgv5dP4K7MDTDRIQj OSGKx2tbwR9pNEJ0Kqf2Zom7kKGjJdWExdblwNTwubC8AIqCgiBkNJzl6W3czUud7/zSgL DbTT1QQ5h2SmMf3+IivQyuk4CcXAiHM= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-588-gnIj684mP2G3ji6veUSnkw-1; Mon, 24 Nov 2025 14:48:08 -0500 X-MC-Unique: gnIj684mP2G3ji6veUSnkw-1 X-Mimecast-MFC-AGG-ID: gnIj684mP2G3ji6veUSnkw_1764013687 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 7BD661800358 for ; Mon, 24 Nov 2025 19:48:07 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3AC7E19560B2 for ; Mon, 24 Nov 2025 19:48:06 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 4/7] Extract from ldconfig In-Reply-To: Message-ID: <33f5e98fbf90886d7ae5ed3398c54e02035b6429.1764012721.git.fweimer@redhat.com> References: X-From-Line: 33f5e98fbf90886d7ae5ed3398c54e02035b6429 Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:48:03 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: QHHBlcvDzWt5C0GIy3h91bJeauoltL-2qPyWbOonYTs_1764013687 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org And make it usable in other parts of the library. The type safety mechanism follows the dynarray approach. The previous implementation now lives in elf/cachestrings.h and elf/cachestrings.c and is layered on top of and . The internal support routines are built for libsupport, so that they can be used in tests. --- elf/Makefile | 4 +- elf/cache.c | 28 +-- elf/cachestrings.c | 149 +++++++++++++ elf/{stringtable.h => cachestrings.h} | 40 ++-- elf/stringtable.c | 209 ------------------ elf/{tst-stringtable.c => tst-cachestrings.c} | 128 ++++++----- include/stringtable-skeleton.h | 128 +++++++++++ include/stringtable.h | 54 +++++ misc/Makefile | 3 + elf/stringtable_free.c => misc/fnv1a.c | 24 +- misc/stringtable_add.c | 127 +++++++++++ misc/stringtable_free.c | 43 ++++ support/Makefile | 1 + support/support_stringtable.c | 20 ++ 14 files changed, 638 insertions(+), 320 deletions(-) create mode 100644 elf/cachestrings.c rename elf/{stringtable.h => cachestrings.h} (53%) delete mode 100644 elf/stringtable.c rename elf/{tst-stringtable.c => tst-cachestrings.c} (54%) create mode 100644 include/stringtable-skeleton.h create mode 100644 include/stringtable.h rename elf/stringtable_free.c => misc/fnv1a.c (63%) create mode 100644 misc/stringtable_add.c create mode 100644 misc/stringtable_free.c create mode 100644 support/support_stringtable.c diff --git a/elf/Makefile b/elf/Makefile index 07b456f4f5..a99f22d717 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -225,10 +225,10 @@ install-rootsbin += ldconfig ldconfig-modules := \ cache \ + cachestrings \ chroot_canon \ readlib \ static-stubs \ - stringtable \ xmalloc \ xstrdup \ # ldconfig-modules @@ -314,11 +314,11 @@ tests := \ tst-array4 \ tst-array5 \ tst-auxv \ + tst-cachestrings \ tst-decorate-maps \ tst-dl-hash \ tst-env-setuid \ tst-leaks1 \ - tst-stringtable \ tst-tls9 \ tst-tunables-enable_secure-env \ # tests diff --git a/elf/cache.c b/elf/cache.c index 89a6a1e49a..e7959dd07e 100644 --- a/elf/cache.c +++ b/elf/cache.c @@ -35,10 +35,10 @@ #include #include #include -#include +#include /* Used to store library names, paths, and other strings. */ -static struct stringtable strings; +static struct cachestrings strings; /* Keeping track of "glibc-hwcaps" subdirectories. During cache construction, a linear search by name is performed to deduplicate @@ -48,7 +48,7 @@ struct glibc_hwcaps_subdirectory struct glibc_hwcaps_subdirectory *next; /* Interned string with the subdirectory name. */ - struct stringtable_entry *name; + struct cachestrings_entry *name; /* Array index in the cache_extension_tag_glibc_hwcaps section in the stored cached file. This is computed after all the @@ -63,7 +63,7 @@ struct glibc_hwcaps_subdirectory const char * glibc_hwcaps_subdirectory_name (const struct glibc_hwcaps_subdirectory *dir) { - return dir->name->string; + return dir->name->E.string; } /* Linked list of known hwcaps subdirectory names. */ @@ -72,7 +72,7 @@ static struct glibc_hwcaps_subdirectory *hwcaps; struct glibc_hwcaps_subdirectory * new_glibc_hwcaps_subdirectory (const char *name) { - struct stringtable_entry *name_interned = stringtable_add (&strings, name); + struct cachestrings_entry *name_interned = cachestrings_add (&strings, name); for (struct glibc_hwcaps_subdirectory *p = hwcaps; p != NULL; p = p->next) if (p->name == name_interned) return p; @@ -141,8 +141,8 @@ assign_glibc_hwcaps_indices (void) struct cache_entry { - struct stringtable_entry *lib; /* Library name. */ - struct stringtable_entry *path; /* Path to find library. */ + struct cachestrings_entry *lib; /* Library name. */ + struct cachestrings_entry *path; /* Path to find library. */ int flags; /* Flags to indicate kind of library. */ unsigned int isa_level; /* Required ISA level. */ @@ -412,7 +412,7 @@ static int compare (const struct cache_entry *e1, const struct cache_entry *e2) { /* We need to swap entries here to get the correct sort order. */ - int res = _dl_cache_libcmp (e2->lib->string, e1->lib->string); + int res = _dl_cache_libcmp (e2->lib->E.string, e1->lib->E.string); if (res == 0) { if (e1->flags < e2->flags) @@ -519,7 +519,7 @@ compute_hwcap_value (struct cache_entry *entry) { if (entry->isa_level > DL_CACHE_HWCAP_ISA_LEVEL_MASK) error (EXIT_FAILURE, 0, _("%s: ISA level is too high (%d > %d)"), - entry->path->string, entry->isa_level, + entry->path->E.string, entry->isa_level, DL_CACHE_HWCAP_ISA_LEVEL_MASK); return (DL_CACHE_HWCAP_EXTENSION | (((uint64_t) entry->isa_level) << 32) @@ -548,8 +548,8 @@ save_cache (const char *cache_name) ++cache_entry_old_count; } - struct stringtable_finalized strings_finalized; - stringtable_finalize (&strings, &strings_finalized); + struct cachestrings_finalized strings_finalized; + cachestrings_finalize (&strings, &strings_finalized); /* Create the on disk cache structure. */ struct cache_file *file_entries = NULL; @@ -758,16 +758,16 @@ add_to_cache (const char *path, const char *filename, const char *soname, { struct cache_entry *new_entry = xmalloc (sizeof (*new_entry)); - struct stringtable_entry *path_interned; + struct cachestrings_entry *path_interned; { char *p; if (asprintf (&p, "%s/%s", path, filename) < 0) error (EXIT_FAILURE, errno, _("Could not create library path")); - path_interned = stringtable_add (&strings, p); + path_interned = cachestrings_add (&strings, p); free (p); } - new_entry->lib = stringtable_add (&strings, soname); + new_entry->lib = cachestrings_add (&strings, soname); new_entry->path = path_interned; new_entry->flags = flags; new_entry->isa_level = isa_level; diff --git a/elf/cachestrings.c b/elf/cachestrings.c new file mode 100644 index 0000000000..68f990d974 --- /dev/null +++ b/elf/cachestrings.c @@ -0,0 +1,149 @@ +/* String tables for ld.so.cache construction. Implementation. + Copyright (C) 2020-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define STRINGTABLE_ENTRY cachestrings_entry +#define STRINGTABLE_STRUCT cachestrings +#define STRINGTABLE_PREFIX cache_s_table_ +#include + +struct cachestrings_entry * +cachestrings_add (struct cachestrings *table, const char *string) +{ + struct cachestrings_entry *e = cache_s_table_add (table, string); + if (e == NULL) + error (EXIT_FAILURE, errno, _("String table is too large")); + return e; +} + +void +cachestrings_free (struct cachestrings *table) +{ + cache_s_table_free (table); +} + +/* Sort reversed strings in reverse lexicographic order. This is used + for tail merging. */ +static int +finalize_compare (const void *l, const void *r) +{ + struct cachestrings_entry *left = *(struct cachestrings_entry **) l; + struct cachestrings_entry *right = *(struct cachestrings_entry **) r; + size_t to_compare; + if (left->E.length < right->E.length) + to_compare = left->E.length; + else + to_compare = right->E.length; + for (size_t i = 1; i <= to_compare; ++i) + { + unsigned char lch = left->E.string[left->E.length - i]; + unsigned char rch = right->E.string[right->E.length - i]; + if (lch != rch) + return rch - lch; + } + if (left->E.length == right->E.length) + return 0; + else if (left->E.length < right->E.length) + /* Longer strings should come first. */ + return 1; + else + return -1; +} + +/* Add E to the array (identified via CLOSURE). */ +static void +cache_s_table_cb_list (struct cachestrings_entry *e, void *closure) +{ + struct cachestrings_entry ***pptr = closure; + **pptr = e; + ++*pptr; +} + +/* Copy the string of E into the result buffer (a struct + cachestrings_finalized *). */ +static void +cache_s_table_cb_store (struct cachestrings_entry *e, void *closure) +{ + struct cachestrings_finalized *result = closure; + if (result->strings[e->offset] == '\0') + memcpy (&result->strings[e->offset], e->E.string, e->E.length + 1); +} + +void +cachestrings_finalize (struct cachestrings *table, + struct cachestrings_finalized *result) +{ + size_t table_count = table->T.count; + if (table_count == 0) + { + result->strings = xstrdup (""); + result->size = 0; + return; + } + + /* Optimize the order of the strings. */ + struct cachestrings_entry **array + = xcalloc (table_count, sizeof (*array)); + { + struct cachestrings_entry **tmp = array; + cache_s_table_foreach (table, cache_s_table_cb_list, &tmp); + assert (tmp == array + table_count); + } + qsort (array, table_count, sizeof (*array), finalize_compare); + + /* Assign offsets, using tail merging (sharing suffixes) if possible. */ + array[0]->offset = 0; + for (uint32_t j = 1; j < table_count; ++j) + { + struct cachestrings_entry *previous = array[j - 1]; + struct cachestrings_entry *current = array[j]; + if (previous->E.length >= current->E.length + && memcmp (&previous->E.string[previous->E.length + - current->E.length], + current->E.string, current->E.length) == 0) + current->offset = (previous->offset + previous->E.length + - current->E.length); + else if (__builtin_add_overflow (previous->offset, + previous->E.length + 1, + ¤t->offset)) + error (EXIT_FAILURE, 0, _("String table is too large")); + } + + /* Allocate the result string. */ + { + struct cachestrings_entry *last = array[table_count - 1]; + if (__builtin_add_overflow (last->offset, last->E.length + 1, + &result->size)) + error (EXIT_FAILURE, 0, _("String table is too large")); + } + /* The strings are copied from the hash table, so the array is no + longer needed. */ + free (array); + result->strings = xcalloc (result->size, 1); + + /* Copy the strings. */ + cache_s_table_foreach (table, cache_s_table_cb_store, result); +} diff --git a/elf/stringtable.h b/elf/cachestrings.h similarity index 53% rename from elf/stringtable.h rename to elf/cachestrings.h index 62711bbee3..bcacd92bb2 100644 --- a/elf/stringtable.h +++ b/elf/cachestrings.h @@ -15,37 +15,35 @@ You should have received a copy of the GNU General Public License along with this program; if not, see . */ -#ifndef _STRINGTABLE_H -#define _STRINGTABLE_H +#ifndef CACHESTRINGS_H +#define CACHESTRINGS_H #include #include +#include -/* An entry in the string table. Only the length and string fields are - expected to be used outside the string table code. */ -struct stringtable_entry +/* An entry in the string table. Only the entry.length and + entry.string fields are expected to be used outside the string + table code. */ +struct cachestrings_entry { - struct stringtable_entry *next; /* For collision resolution. */ - uint32_t length; /* Length of then string. */ - uint32_t offset; /* From start of finalized table. */ - char string[]; /* Null-terminated string. */ + size_t offset; /* From start of finalized table. */ + struct stringtable_entry E; }; -/* A string table. Zero-initialization produces a valid atable. */ -struct stringtable +/* String table for use with ldconfig cache strings. */ +struct cachestrings { - struct stringtable_entry **entries; /* Array of hash table buckets. */ - uint32_t count; /* Number of elements in the table. */ - uint32_t allocated; /* Length of the entries array. */ + struct stringtable T; }; /* Adds STRING to TABLE. May return the address of an existing entry. */ -struct stringtable_entry *stringtable_add (struct stringtable *table, - const char *string); +struct cachestrings_entry *cachestrings_add (struct cachestrings *table, + const char *string); /* Result of stringtable_finalize. SIZE bytes at STRINGS should be written to the file. */ -struct stringtable_finalized +struct cachestrings_finalized { char *strings; size_t size; @@ -53,12 +51,12 @@ struct stringtable_finalized /* Assigns offsets to string table entries and computes the serialized form of the string table. */ -void stringtable_finalize (struct stringtable *table, - struct stringtable_finalized *result); +void cachestrings_finalize (struct cachestrings *, + struct cachestrings_finalized *); /* Deallocate the string table (but not the TABLE pointer itself). (The table can be re-used for adding more strings without initialization.) */ -void stringtable_free (struct stringtable *table); +void cachestrings_free (struct cachestrings *table); -#endif /* _STRINGTABLE_H */ +#endif /* CACHESTRINGS_H */ diff --git a/elf/stringtable.c b/elf/stringtable.c deleted file mode 100644 index 89d1952425..0000000000 --- a/elf/stringtable.c +++ /dev/null @@ -1,209 +0,0 @@ -/* String tables for ld.so.cache construction. Implementation. - Copyright (C) 2020-2025 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published - by the Free Software Foundation; version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see . */ - -#include -#include -#include -#include -#include -#include -#include - -static void -stringtable_init (struct stringtable *table) -{ - table->count = 0; - - /* This needs to be a power of two. 128 is sufficient to keep track - of 42 DSOs without resizing (assuming two strings per DSOs). - glibc itself comes with more than 20 DSOs, so 64 would likely to - be too small. */ - table->allocated = 128; - - table->entries = xcalloc (table->allocated, sizeof (table->entries[0])); -} - -/* 32-bit FNV-1a hash function. */ -static uint32_t -fnv1a (const char *string, size_t length) -{ - const unsigned char *p = (const unsigned char *) string; - uint32_t hash = 2166136261U; - for (size_t i = 0; i < length; ++i) - { - hash ^= p[i]; - hash *= 16777619U; - } - return hash; -} - -/* Double the capacity of the hash table. */ -static void -stringtable_rehash (struct stringtable *table) -{ - /* This computation cannot overflow because the old total in-memory - size of the hash table is larger than the computed value. */ - uint32_t new_allocated = table->allocated * 2; - struct stringtable_entry **new_entries - = xcalloc (new_allocated, sizeof (table->entries[0])); - - uint32_t mask = new_allocated - 1; - for (uint32_t i = 0; i < table->allocated; ++i) - for (struct stringtable_entry *e = table->entries[i]; e != NULL; ) - { - struct stringtable_entry *next = e->next; - uint32_t hash = fnv1a (e->string, e->length); - uint32_t new_index = hash & mask; - e->next = new_entries[new_index]; - new_entries[new_index] = e; - e = next; - } - - free (table->entries); - table->entries = new_entries; - table->allocated = new_allocated; -} - -struct stringtable_entry * -stringtable_add (struct stringtable *table, const char *string) -{ - /* Check for a zero-initialized table. */ - if (table->allocated == 0) - stringtable_init (table); - - size_t length = strlen (string); - if (length > (1U << 30)) - error (EXIT_FAILURE, 0, _("String table string is too long")); - uint32_t hash = fnv1a (string, length); - - /* Return a previously-existing entry. */ - for (struct stringtable_entry *e - = table->entries[hash & (table->allocated - 1)]; - e != NULL; e = e->next) - if (e->length == length && memcmp (e->string, string, length) == 0) - return e; - - /* Increase the size of the table if necessary. Keep utilization - below two thirds. */ - if (table->count >= (1U << 30)) - error (EXIT_FAILURE, 0, _("String table has too many entries")); - if (table->count * 3 > table->allocated * 2) - stringtable_rehash (table); - - /* Add the new table entry. */ - ++table->count; - struct stringtable_entry *e - = xmalloc (offsetof (struct stringtable_entry, string) + length + 1); - uint32_t index = hash & (table->allocated - 1); - e->next = table->entries[index]; - table->entries[index] = e; - e->length = length; - e->offset = 0; - memcpy (e->string, string, length + 1); - return e; -} - -/* Sort reversed strings in reverse lexicographic order. This is used - for tail merging. */ -static int -finalize_compare (const void *l, const void *r) -{ - struct stringtable_entry *left = *(struct stringtable_entry **) l; - struct stringtable_entry *right = *(struct stringtable_entry **) r; - size_t to_compare; - if (left->length < right->length) - to_compare = left->length; - else - to_compare = right->length; - for (size_t i = 1; i <= to_compare; ++i) - { - unsigned char lch = left->string[left->length - i]; - unsigned char rch = right->string[right->length - i]; - if (lch != rch) - return rch - lch; - } - if (left->length == right->length) - return 0; - else if (left->length < right->length) - /* Longer strings should come first. */ - return 1; - else - return -1; -} - -void -stringtable_finalize (struct stringtable *table, - struct stringtable_finalized *result) -{ - if (table->count == 0) - { - result->strings = xstrdup (""); - result->size = 0; - return; - } - - /* Optimize the order of the strings. */ - struct stringtable_entry **array = xcalloc (table->count, sizeof (*array)); - { - size_t j = 0; - for (uint32_t i = 0; i < table->allocated; ++i) - for (struct stringtable_entry *e = table->entries[i]; e != NULL; - e = e->next) - { - array[j] = e; - ++j; - } - assert (j == table->count); - } - qsort (array, table->count, sizeof (*array), finalize_compare); - - /* Assign offsets, using tail merging (sharing suffixes) if possible. */ - array[0]->offset = 0; - for (uint32_t j = 1; j < table->count; ++j) - { - struct stringtable_entry *previous = array[j - 1]; - struct stringtable_entry *current = array[j]; - if (previous->length >= current->length - && memcmp (&previous->string[previous->length - current->length], - current->string, current->length) == 0) - current->offset = (previous->offset + previous->length - - current->length); - else if (__builtin_add_overflow (previous->offset, - previous->length + 1, - ¤t->offset)) - error (EXIT_FAILURE, 0, _("String table is too large")); - } - - /* Allocate the result string. */ - { - struct stringtable_entry *last = array[table->count - 1]; - if (__builtin_add_overflow (last->offset, last->length + 1, - &result->size)) - error (EXIT_FAILURE, 0, _("String table is too large")); - } - /* The strings are copied from the hash table, so the array is no - longer needed. */ - free (array); - result->strings = xcalloc (result->size, 1); - - /* Copy the strings. */ - for (uint32_t i = 0; i < table->allocated; ++i) - for (struct stringtable_entry *e = table->entries[i]; e != NULL; - e = e->next) - if (result->strings[e->offset] == '\0') - memcpy (&result->strings[e->offset], e->string, e->length + 1); -} diff --git a/elf/tst-stringtable.c b/elf/tst-cachestrings.c similarity index 54% rename from elf/tst-stringtable.c rename to elf/tst-cachestrings.c index 5fffb0de1b..7daa8838d0 100644 --- a/elf/tst-stringtable.c +++ b/elf/tst-cachestrings.c @@ -19,7 +19,8 @@ #include #if __GNUC_PREREQ (5, 0) #include -#include +#define attribute_hidden +#include #include #include @@ -28,72 +29,75 @@ do_test (void) { /* Empty string table. */ { - struct stringtable s = { 0, }; - struct stringtable_finalized f; - stringtable_finalize (&s, &f); + struct cachestrings s = { 0, }; + struct cachestrings_finalized f; + cachestrings_finalize (&s, &f); TEST_COMPARE_STRING (f.strings, ""); TEST_COMPARE (f.size, 0); free (f.strings); - stringtable_free (&s); + cachestrings_free (&s); } /* String table with one empty string. */ { - struct stringtable s = { 0, }; - struct stringtable_entry *e = stringtable_add (&s, ""); - TEST_COMPARE_STRING (e->string, ""); - TEST_COMPARE (e->length, 0); - TEST_COMPARE (s.count, 1); - - struct stringtable_finalized f; - stringtable_finalize (&s, &f); + struct cachestrings s = { 0, }; + struct cachestrings_entry *e = cachestrings_add (&s, ""); + TEST_COMPARE_STRING (e->E.string, ""); + TEST_COMPARE (e->E.length, 0); + TEST_COMPARE (e->offset, 0); + TEST_COMPARE (s.T.count, 1); + + struct cachestrings_finalized f; + cachestrings_finalize (&s, &f); TEST_COMPARE (e->offset, 0); TEST_COMPARE_STRING (f.strings, ""); TEST_COMPARE (f.size, 1); free (f.strings); - stringtable_free (&s); + cachestrings_free (&s); } /* String table with one non-empty string. */ { - struct stringtable s = { 0, }; - struct stringtable_entry *e = stringtable_add (&s, "name"); - TEST_COMPARE_STRING (e->string, "name"); - TEST_COMPARE (e->length, 4); - TEST_COMPARE (s.count, 1); - - struct stringtable_finalized f; - stringtable_finalize (&s, &f); + struct cachestrings s = { 0, }; + struct cachestrings_entry *e = cachestrings_add (&s, "name"); + TEST_COMPARE_STRING (e->E.string, "name"); + TEST_COMPARE (e->E.length, 4); + TEST_COMPARE (e->offset, 0); + TEST_COMPARE (s.T.count, 1); + + struct cachestrings_finalized f; + cachestrings_finalize (&s, &f); TEST_COMPARE (e->offset, 0); TEST_COMPARE_STRING (f.strings, "name"); TEST_COMPARE (f.size, 5); free (f.strings); - stringtable_free (&s); + cachestrings_free (&s); } /* Two strings, one is a prefix of the other. Tail-merging can only happen in one way in this case. */ { - struct stringtable s = { 0, }; - struct stringtable_entry *suffix = stringtable_add (&s, "suffix"); - TEST_COMPARE_STRING (suffix->string, "suffix"); - TEST_COMPARE (suffix->length, 6); - TEST_COMPARE (s.count, 1); - - struct stringtable_entry *prefix - = stringtable_add (&s, "prefix-suffix"); - TEST_COMPARE_STRING (prefix->string, "prefix-suffix"); - TEST_COMPARE (prefix->length, strlen ("prefix-suffix")); - TEST_COMPARE (s.count, 2); - - struct stringtable_finalized f; - stringtable_finalize (&s, &f); + struct cachestrings s = { 0, }; + struct cachestrings_entry *suffix + = cachestrings_add (&s, "suffix"); + TEST_COMPARE_STRING (suffix->E.string, "suffix"); + TEST_COMPARE (suffix->E.length, 6); + TEST_COMPARE (s.T.count, 1); + + struct cachestrings_entry *prefix + = cachestrings_add (&s, "prefix-suffix"); + TEST_COMPARE_STRING (prefix->E.string, "prefix-suffix"); + TEST_COMPARE (prefix->E.length, strlen ("prefix-suffix")); + TEST_COMPARE (s.T.count, 2); + + struct cachestrings_finalized f; + cachestrings_finalize (&s, &f); TEST_COMPARE (prefix->offset, 0); TEST_COMPARE (suffix->offset, strlen ("prefix-")); TEST_COMPARE_STRING (f.strings, "prefix-suffix"); TEST_COMPARE (f.size, sizeof ("prefix-suffix")); free (f.strings); - stringtable_free (&s); + cachestrings_free (&s); } /* String table with various shared prefixes. Triggers hash @@ -101,38 +105,40 @@ do_test (void) { enum { count = 1500 }; char *strings[2 * count]; - struct stringtable_entry *entries[2 * count]; - struct stringtable s = { 0, }; + struct cachestrings_entry *entries[2 * count]; + struct cachestrings s = { 0, }; for (int i = 0; i < count; ++i) { strings[i] = xasprintf ("%d", i); - entries[i] = stringtable_add (&s, strings[i]); - TEST_COMPARE (entries[i]->length, strlen (strings[i])); - TEST_COMPARE_STRING (entries[i]->string, strings[i]); + entries[i] = cachestrings_add (&s, strings[i]); + TEST_COMPARE (entries[i]->E.length, strlen (strings[i])); + TEST_COMPARE_STRING (entries[i]->E.string, strings[i]); strings[i + count] = xasprintf ("prefix/%d", i); - entries[i + count] = stringtable_add (&s, strings[i + count]); - TEST_COMPARE (entries[i + count]->length, strlen (strings[i + count])); - TEST_COMPARE_STRING (entries[i + count]->string, strings[i + count]); + entries[i + count] = cachestrings_add (&s, strings[i + count]); + TEST_COMPARE (entries[i + count]->E.length, + strlen (strings[i + count])); + TEST_COMPARE_STRING (entries[i + count]->E.string, + strings[i + count]); } - struct stringtable_finalized f; - stringtable_finalize (&s, &f); + struct cachestrings_finalized f; + cachestrings_finalize (&s, &f); for (int i = 0; i < 2 * count; ++i) { - TEST_COMPARE (entries[i]->length, strlen (strings[i])); - TEST_COMPARE_STRING (entries[i]->string, strings[i]); + TEST_COMPARE (entries[i]->E.length, strlen (strings[i])); + TEST_COMPARE_STRING (entries[i]->E.string, strings[i]); TEST_COMPARE_STRING (f.strings + entries[i]->offset, strings[i]); free (strings[i]); } free (f.strings); - stringtable_free (&s); + cachestrings_free (&s); } /* Verify that maximum tail merging happens. */ { - struct stringtable s = { 0, }; + struct cachestrings s = { 0, }; const char *strings[] = { "", "a", @@ -146,14 +152,14 @@ do_test (void) "ba", "baa", }; - struct stringtable_entry *entries[array_length (strings)]; + struct cachestrings_entry *entries[array_length (strings)]; for (int i = 0; i < array_length (strings); ++i) - entries[i] = stringtable_add (&s, strings[i]); + entries[i] = cachestrings_add (&s, strings[i]); for (int i = 0; i < array_length (strings); ++i) - TEST_COMPARE_STRING (entries[i]->string, strings[i]); + TEST_COMPARE_STRING (entries[i]->E.string, strings[i]); - struct stringtable_finalized f; - stringtable_finalize (&s, &f); + struct cachestrings_finalized f; + cachestrings_finalize (&s, &f); /* There are only four different strings, "aaa", "ba", "baa", "bb". The rest is shared in an unspecified fashion. */ @@ -161,12 +167,12 @@ do_test (void) for (int i = 0; i < array_length (strings); ++i) { - TEST_COMPARE_STRING (entries[i]->string, strings[i]); + TEST_COMPARE_STRING (entries[i]->E.string, strings[i]); TEST_COMPARE_STRING (f.strings + entries[i]->offset, strings[i]); } free (f.strings); - stringtable_free (&s); + cachestrings_free (&s); } return 0; @@ -178,8 +184,8 @@ do_test (void) possible to link against the actual build because it was built for use in ldconfig. */ #define _(arg) arg -#include "stringtable.c" -#include "stringtable_free.c" +#define __set_errno(code) (void) (errno = (code)) +#include "cachestrings.c" #else #include diff --git a/include/stringtable-skeleton.h b/include/stringtable-skeleton.h new file mode 100644 index 0000000000..d48a800efc --- /dev/null +++ b/include/stringtable-skeleton.h @@ -0,0 +1,128 @@ +/* Template for defining string tables. + Copyright (C) 2020-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +/* To use the stringtable facility, you need to include + and define the parameter macros. + + The tables are intrusive: you need to define your own types, + embedding struct stringtable_entry and struct stringtable. + + A minimal example looks like this: + + #include + + struct table_entry + { + struct stringtable_entry E; + }; + + struct table + { + struct stringtable T; + }; + + #define STRINGTABLE_ENTRY table_entry + #define STRINGTABLE_STRUCT table + #define STRINGTABLE_PREFIX table_ + #include + + The struct STRINGTABLE_ENTRY type must have a + + struct stringtable_entry E; + + member at the end. + + The struct STRINGTABLE_STRUCT type must have a + + struct stringtable T; + + member (not necessarily at the end). The T member must be + zero-initialized before the functions described below are called. + + Including defines the following functions: + + STRINGTABLE_ENTRY * + STRINGTABLE_PREFIX_add (struct STRINGTABLE_STRUCT *table, + const char *string); + + Add STRING to TABLE. Returns NULL on memory allocation failure. + + void STRINGTABLE_PREFIX_free (struct STRINGTABLE_STRUCT *table); + + Deallocate TABLE and all its entries. + + void STRINGTABLE_PREFIX_foreach (struct STRINGTABLE_STRUCT *table, + void (*cb) (struct STRINGTABLE_ENTRY *, + void *), + void *closure); + + Iterate over all entries in TABLE, calling CB for each entry + with CLOSURE as the second argument. +*/ + +#include +#include +#include + +#define STRINGTABLE_ENTRY_OFFSET (offsetof (struct STRINGTABLE_ENTRY, E)) + +_Static_assert (STRINGTABLE_ENTRY_OFFSET + sizeof (struct stringtable_entry) + == sizeof (struct STRINGTABLE_ENTRY), + "entry member must come last"); + +#define STRINGTABLE_CONCAT0(prefix, name) prefix##name +#define STRINGTABLE_CONCAT1(prefix, name) STRINGTABLE_CONCAT0(prefix, name) +#define STRINGTABLE_NAME(name) STRINGTABLE_CONCAT1(STRINGTABLE_PREFIX, name) + + +__attribute__ ((unused)) static struct STRINGTABLE_ENTRY * +STRINGTABLE_NAME(add) (struct STRINGTABLE_STRUCT *table, + const char *string) +{ + return __stringtable_add (&table->T, string, STRINGTABLE_ENTRY_OFFSET); +} + +__attribute__ ((unused)) static void +STRINGTABLE_NAME(free) (struct STRINGTABLE_STRUCT *table) +{ + __stringtable_free (&table->T, STRINGTABLE_ENTRY_OFFSET); +} + +static inline void +STRINGTABLE_NAME(foreach) (struct STRINGTABLE_STRUCT *table, + void (*callback) (struct STRINGTABLE_ENTRY *, + void *), + void *closure) +{ + struct stringtable_entry **p = table->T.entries; + if (p == NULL) + return; + struct stringtable_entry **end = p + table->T.allocated; + for (; p != end; ++p) + for (struct stringtable_entry *e = *p; e != NULL; e = e->next) + callback ((struct STRINGTABLE_ENTRY *) ((char *) e - + STRINGTABLE_ENTRY_OFFSET), + closure); +} + +#undef STRINGTABLE_CONCAT0 +#undef STRINGTABLE_CONCAT1 +#undef STRINGTABLE_NAME +#undef STRINGTABLE_ENTRY_OFFSET +#undef STRINGTABLE_ENTRY +#undef STRINGTABLE_STRUCT +#undef STRINGTABLE_PREFIX diff --git a/include/stringtable.h b/include/stringtable.h new file mode 100644 index 0000000000..7176dbd7c1 --- /dev/null +++ b/include/stringtable.h @@ -0,0 +1,54 @@ +/* Support facilities for string tables. + Copyright (C) 2020-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +/* See for a way to use this file. */ + +#ifndef STRINGTABLE_H +#define STRINGTABLE_H + +#include +#include + +/* Common part of hash table entries. Used for the E member. */ +struct stringtable_entry +{ + struct stringtable_entry *next; /* For collision resolution. */ + uint32_t length; /* Length of the string. */ + char string[]; /* Null-terminated string. */ +}; + +/* Common fields of a string table. Used for the T member. + Zero-initialization produces a valid table. */ +struct stringtable +{ + struct stringtable_entry **entries; /* Array of hash table buckets. */ + uint32_t count; /* Number of elements in the table. */ + uint32_t allocated; /* Length of the entries array. */ +}; + +/* 32-bit FNV-1a hash function. */ +uint32_t __fnv1a (const char *string, size_t length) attribute_hidden; + +/* Internal functions. ENTRY_OFFSET is the offset of the + stringtable_entry structs from the start of the malloc + allocation. */ +void *__stringtable_add (struct stringtable *, const char *, + size_t entry_offset) attribute_hidden; +void __stringtable_free (struct stringtable *, size_t entry_offset) + attribute_hidden; + +#endif /* STRINGTABLE_H */ diff --git a/misc/Makefile b/misc/Makefile index e792d94ebd..a5df61574a 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -99,6 +99,7 @@ routines := \ fdatasync \ fgetxattr \ flistxattr \ + fnv1a \ fremovexattr \ fsetxattr \ fstab \ @@ -187,6 +188,8 @@ routines := \ setxattr \ single_threaded \ sstk \ + stringtable_add \ + stringtable_free \ stty \ swapoff \ swapon \ diff --git a/elf/stringtable_free.c b/misc/fnv1a.c similarity index 63% rename from elf/stringtable_free.c rename to misc/fnv1a.c index 0141b4132b..6dc35e2b3e 100644 --- a/elf/stringtable_free.c +++ b/misc/fnv1a.c @@ -1,4 +1,4 @@ -/* String tables for ld.so.cache construction. Deallocation (for tests only). +/* Simple hash function, used in string tables. Copyright (C) 2020-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -15,19 +15,17 @@ You should have received a copy of the GNU General Public License along with this program; if not, see . */ -#include #include -void -stringtable_free (struct stringtable *table) +uint32_t +__fnv1a (const char *string, size_t length) { - for (uint32_t i = 0; i < table->allocated; ++i) - for (struct stringtable_entry *e = table->entries[i]; e != NULL; ) - { - struct stringtable_entry *next = e->next; - free (e); - e = next; - } - free (table->entries); - *table = (struct stringtable) { 0, }; + const unsigned char *p = (const unsigned char *) string; + uint32_t hash = 2166136261U; + for (size_t i = 0; i < length; ++i) + { + hash ^= p[i]; + hash *= 16777619U; + } + return hash; } diff --git a/misc/stringtable_add.c b/misc/stringtable_add.c new file mode 100644 index 0000000000..6c71a19d2e --- /dev/null +++ b/misc/stringtable_add.c @@ -0,0 +1,127 @@ +/* Generic implementation for adding strings to string tables. + Copyright (C) 2020-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +#include + +#include +#include +#include +#include + +static bool +__stringtable_init (struct stringtable *table) +{ + table->count = 0; + + /* This needs to be a power of two. */ + table->allocated = 16; + + table->entries = calloc (table->allocated, sizeof (table->entries[0])); + return table->entries != NULL; +} + +/* Double the capacity of the hash table. */ +static bool +__stringtable_rehash (struct stringtable *table) +{ + uint32_t new_allocated = table->allocated * 2; + if (new_allocated < table->allocated) + { + __set_errno (ENOMEM); + return false; + } + + struct stringtable_entry **new_entries + = calloc (new_allocated, sizeof (table->entries[0])); + if (new_entries == NULL) + return false; + + uint32_t mask = new_allocated - 1; + for (uint32_t i = 0; i < table->allocated; ++i) + for (struct stringtable_entry *e = table->entries[i]; e != NULL; ) + { + struct stringtable_entry *next = e->next; + uint32_t hash = __fnv1a (e->string, e->length); + uint32_t new_index = hash & mask; + e->next = new_entries[new_index]; + new_entries[new_index] = e; + e = next; + } + + free (table->entries); + table->entries = new_entries; + table->allocated = new_allocated; + return true; +} + +void * +__stringtable_add (struct stringtable *table, const char *string, + size_t entry_offset) +{ + /* Check for a zero-initialized table. */ + if (table->allocated == 0 && !__stringtable_init (table)) + return NULL; + + size_t length = strlen (string); + if (length != (uint32_t) length) + { + /* String is too long to store. */ + __set_errno (ENOMEM); + return NULL; + } + + uint32_t hash = __fnv1a (string, length); + + /* Return a previously-existing entry. */ + for (struct stringtable_entry *e + = table->entries[hash & (table->allocated - 1)]; + e != NULL; e = e->next) + if (e->length == length && memcmp (e->string, string, length) == 0) + return (char *) e - entry_offset; + + /* Increase the size of the table if necessary. Keep utilization + below two thirds. */ + if (table->count >= UINT32_MAX / 3) + { + __set_errno (ENOMEM); + return NULL; + } + if (table->count * 3 > table->allocated * 2) + if (!__stringtable_rehash (table)) + return NULL; + + /* Add the new table entry. No overflow is possible because length + must be less than half of the address space size. */ + char *base = malloc (entry_offset + + offsetof (struct stringtable_entry, string) + + length + 1); + if (base == NULL) + return NULL; + /* Extra data is zero-initialized. */ + memset (base, 0, entry_offset); + + struct stringtable_entry *e + = (struct stringtable_entry *) (base + entry_offset); + e->length = length; + memcpy (e->string, string, length + 1); + + ++table->count; + uint32_t index = hash & (table->allocated - 1); + e->next = table->entries[index]; + table->entries[index] = e; + return (char *) e - entry_offset; +} diff --git a/misc/stringtable_free.c b/misc/stringtable_free.c new file mode 100644 index 0000000000..e7204010f2 --- /dev/null +++ b/misc/stringtable_free.c @@ -0,0 +1,43 @@ +/* Generic implementation for freeing string tables. + Copyright (C) 2020-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +#include + +#include +#include + +void +__stringtable_free (struct stringtable *table, + size_t entry_offset) +{ + size_t allocated = table->allocated; + for (size_t i = 0; i < allocated; ++i) + { + struct stringtable_entry *e = table->entries[i]; + while (e != NULL) + { + struct stringtable_entry *next = e->next; + /* The allocated pointer is at an offset before the entry. */ + free ((char *) e - entry_offset); + e = next; + } + } + free (table->entries); + table->entries = NULL; + table->allocated = 0; + table->count = 0; +} diff --git a/support/Makefile b/support/Makefile index 2043e4e590..dbea4b3726 100644 --- a/support/Makefile +++ b/support/Makefile @@ -93,6 +93,7 @@ libsupport-routines = \ support_socket_so_timestamp_time64 \ support_stack_alloc \ support_stat_nanoseconds \ + support_stringtable \ support_subprocess \ support_test_compare_blob \ support_test_compare_failure \ diff --git a/support/support_stringtable.c b/support/support_stringtable.c new file mode 100644 index 0000000000..aef6d6f6db --- /dev/null +++ b/support/support_stringtable.c @@ -0,0 +1,20 @@ +/* Separate build of stringtables for use in tests. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +#include +#include +#include From patchwork Mon Nov 24 19:48:11 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125191 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2341A385841B for ; Mon, 24 Nov 2025 19:54:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2341A385841B Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=X4pMZg1g X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 9B1353858415 for ; Mon, 24 Nov 2025 19:48:17 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9B1353858415 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 9B1353858415 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013697; cv=none; b=x/oR4Hc13DmqNcymM4xEoYCJZQc2wJUuBElcOQIBNn7DRj7djp6tCUbKdaKuHvWkDBQDNhR6q57jBjDdpeAXmcT1dYB+iMt6OLe1T59VvLa2Xy7UWYV0q+xO6/DXIbmNXYK/fhar9c6u9ibQE1uB85CvpVC37N9/+52bn/Rfjh8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013697; c=relaxed/simple; bh=j8B8AAre2IUR6SOEQ5uAI0K6RubPW6Ue0kMp7Itmar0=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=UAW95YFurnWcT184kPGhgqsuR/LN9qWk9WeVkc/9E14dQuubsSoIZC8nQmJA9qw/pKD/z/l8l3Af+5QzvlxEXYbWndMEGeLpIT7ILJv5iLFJAd5QfWlCJ/7rJ3fDR11BSWy9/PPOPOoaMKCzHluIkkUqwoqbeTktWRxtZ+9HPzA= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9B1353858415 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013697; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=uZXTPmHW+GjlLJf4xBI2ejq4a5y5uoCrBCSTkYEu4z0=; b=X4pMZg1gk9657f6vfj8VcfwoE13UiJB9ns50LzMtfxJIh5KWoGpqiZ2lptP6rmpErpgNHz RkpzVfoEy6F89n+jnYkQIhH1N+3v51ZfVpBdSYnwnh+SlEF19ukCA/Uct0mtM57Vgav5Sj 1eaqUowQNIwxO6xHvJ2QvBXsO6ELvqs= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-672-sIVY1M9iOiuLbChvF-wyQQ-1; Mon, 24 Nov 2025 14:48:15 -0500 X-MC-Unique: sIVY1M9iOiuLbChvF-wyQQ-1 X-Mimecast-MFC-AGG-ID: sIVY1M9iOiuLbChvF-wyQQ_1764013694 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 60BE919560A2 for ; Mon, 24 Nov 2025 19:48:14 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id A4FDC1956056 for ; Mon, 24 Nov 2025 19:48:13 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 5/7] nss: Low-level functionality for merging group lists In-Reply-To: Message-ID: References: X-From-Line: b7ed568e377eb596a33c648b194be87311370ed0 Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:48:11 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: VASZp4odQHRvuklxRdaPXRXXx-tOesbtD7H_ZHvFvB0_1764013694 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org Use to duplicate members, with a single-linked list to preserve ordering. --- nss/Makefile | 3 ++ nss/nss_group_members.c | 80 +++++++++++++++++++++++++++++++++++++ nss/nss_group_members.h | 61 ++++++++++++++++++++++++++++ nss/tst-nss_group_members.c | 77 +++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 nss/nss_group_members.c create mode 100644 nss/nss_group_members.h create mode 100644 nss/tst-nss_group_members.c diff --git a/nss/Makefile b/nss/Makefile index 745a328d6e..718c6f6b9d 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -47,6 +47,7 @@ routines = \ nss_files_functions \ nss_generic_copy \ nss_generic_dup \ + nss_group_members \ nss_hash \ nss_module \ nss_parse_line_result \ @@ -88,6 +89,7 @@ routines += \ getgrnam_r \ grp-merge \ initgroups \ + nss_group_members \ putgrent \ # routines @@ -314,6 +316,7 @@ tests-internal := \ tst-field \ tst-nss_generic_copy \ tst-nss_generic_dup \ + tst-nss_group_members \ tst-rfc3484 \ tst-rfc3484-2 \ tst-rfc3484-3 \ diff --git a/nss/nss_group_members.c b/nss/nss_group_members.c new file mode 100644 index 0000000000..f752772cbb --- /dev/null +++ b/nss/nss_group_members.c @@ -0,0 +1,80 @@ +/* Support for merging group member lists. Implementation. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include + +#include +#include +#include + +void +__nss_group_members_init (struct nss_group_members *table) +{ + *table = (struct nss_group_members) + { + .tail = &table->head, + }; +} + +void +__nss_group_members_free (struct nss_group_members *table) +{ + __nss_group_members__free (table); + __nss_group_members_init (table); +} + +bool +__nss_group_members_add (struct nss_group_members *table, + const struct group *grp) +{ + if (grp->gr_mem == NULL) + return true; + for (char **p = grp->gr_mem; *p != NULL; ++p) + { + struct nss_group_members_entry *e = __nss_group_members__add (table, *p); + if (e == NULL) + return false; + /* If the element is freshly inserted, e->order is NULL. + However, e->order may be NULL if e is not freshly inserted + and e is the last element in the ordering chain, which has no + successor. */ + if (e->order == NULL && &e->order != table->tail) + { + *table->tail = e; + table->tail = &e->order; + } + } + return true; +} + +char ** +__nss_group_members (const struct nss_group_members *table) +{ + char **list = malloc ((table->T.count + 1) * sizeof (*list)); + if (list == NULL) + return NULL; + + size_t i = 0; + for (struct nss_group_members_entry *p = table->head; p != NULL; + p = p->order, ++i) + list[i] = p->E.string; + assert (i == table->T.count); + list[i] = NULL; + + return list; +} diff --git a/nss/nss_group_members.h b/nss/nss_group_members.h new file mode 100644 index 0000000000..0e9b875d29 --- /dev/null +++ b/nss/nss_group_members.h @@ -0,0 +1,61 @@ +/* Support for merging group member lists. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef NSS_GROUP_MEMBERS_H +#define NSS_GROUP_MEMBERS_H + +#include +#include + +struct nss_group_members_entry +{ + struct nss_group_members_entry *order; + struct stringtable_entry E; +}; + +struct nss_group_members +{ + struct stringtable T; + struct nss_group_members_entry *head; + struct nss_group_members_entry **tail; +}; + +#define STRINGTABLE_ENTRY nss_group_members_entry +#define STRINGTABLE_STRUCT nss_group_members +#define STRINGTABLE_PREFIX __nss_group_members__ +#include + +/* Initialize a group members table. */ +void __nss_group_members_init (struct nss_group_members *) attribute_hidden; + +/* Deallocate a group members table. The table is initialized afterwards. */ +void __nss_group_members_free (struct nss_group_members *) attribute_hidden; + +/* Add the members from GRP to TABLE. Returns true on success, false + on memory allocation failure. */ +struct group; +bool __nss_group_members_add (struct nss_group_members *table, + const struct group *grp) attribute_hidden; + +/* Return a NULL-terminated array of strings, in the order they have + been added to the table. The strings point into the table. The + returned pointer should be passed to free. Return NULL on memory + allocation failure. */ +char **__nss_group_members (const struct nss_group_members *) attribute_hidden; + +#endif /* NSS_GROUP_MEMBERS_H */ diff --git a/nss/tst-nss_group_members.c b/nss/tst-nss_group_members.c new file mode 100644 index 0000000000..eb3807d44a --- /dev/null +++ b/nss/tst-nss_group_members.c @@ -0,0 +1,77 @@ +/* Test infrastructure for merging group member lists. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +static int +do_test (void) +{ + struct nss_group_members t; + __nss_group_members_init (&t); + + for (unsigned int count = 0; count < 100; ++count) + { + struct group grp = { }; + grp.gr_mem = xcalloc (count + 1, sizeof (*grp.gr_mem)); + for (unsigned int i = 0; i < count; ++i) + grp.gr_mem[i] = xasprintf ("user%d", i); + + /* This contains the expected list for the result. */ + struct group *grp_copy = __nss_generic_dup (nss_lookup_getgrgid, &grp); + + TEST_VERIFY (__nss_group_members_add (&t, &grp)); + + for (unsigned int i = 0; i < count; ++i) + free (grp.gr_mem[i]); + + /* Add some duplicates in a random-looking order. */ + for (unsigned int i = 0; i < count / 2; ++i) + grp.gr_mem[i] = xasprintf ("user%d", (i * 0x9e3779b9) % count); + grp.gr_mem[count / 2] = NULL; + + /* Merge. */ + TEST_VERIFY (__nss_group_members_add (&t, &grp)); + + for (unsigned int i = 0; i < count / 2; ++i) + free (grp.gr_mem[i]); + free (grp.gr_mem); + + /* Compare the results. */ + char **list = __nss_group_members (&t); + for (unsigned int i = 0; i <= count; ++i) + TEST_COMPARE_STRING (list[i], grp_copy->gr_mem[i]); + + free (list); + __nss_group_members_free (&t); + free (grp_copy); + } + + return 0; +} + +#include + +/* Re-compile the support code because it is not exported from libc. */ +#include +#include +#include From patchwork Mon Nov 24 19:48:16 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125194 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 15DD63858409 for ; Mon, 24 Nov 2025 19:57:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 15DD63858409 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=LARTjqto X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 4F78A3858419 for ; Mon, 24 Nov 2025 19:48:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 4F78A3858419 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 4F78A3858419 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013702; cv=none; b=WID8Z920diwy7SdKH3GfAitElbv89YNIaqd6/+3S0gootWJIp7YEQUoIlhm+dnm3ePsqRJzq8fF55Jt4N0UAzC7gwvcem9n50Zt2xLt6k7o7HykHdaRqFaY5eBQ8LxP5JXDG/gwNT2Zd2FSLP5oqlnDcYyx5djCj5VqaHWxvkTw= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013702; c=relaxed/simple; bh=/G/lof/mP91lS96dCWPrz5efCbp/CUDiUZfind+md3E=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=IA72n5hqYlGpCHUimNCSReQFaHtCakDuJxyl/He/0tskDRM6UK9E5iPCmdZ9yzdcp1TVmekAnT29wuZa+4YVxytBF+5jL/LHwcjOdm08UJxhFW8wYV/yoqGgoKRIM62MyRvcrVWe4Rzw4NoOL+MB3agMxHO9mRszFypADsjD2z4= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4F78A3858419 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013702; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=nYKyRZmJXsqFZYONmEw1jpm5oLESBiIPRrtcowjBQxY=; b=LARTjqtooeI99Lb+8QxeiXvgGGIDpL7PZ0XAisZRjwIW2sby1QqW++CttFAEiurju108/R tcfz9ZVdmQrMo9ewosh9yndEc2JMzcMRJ0CgdI7dJZEQL49dPTwd9+egPXLYJ4h/mZP4hn 6J0J+XOiWQi0kDsPeTvtc4HW3GmzDeU= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-124-CjyJtkVkNDmmzBzgtIGs5w-1; Mon, 24 Nov 2025 14:48:20 -0500 X-MC-Unique: CjyJtkVkNDmmzBzgtIGs5w-1 X-Mimecast-MFC-AGG-ID: CjyJtkVkNDmmzBzgtIGs5w_1764013699 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id BFBAA180045C for ; Mon, 24 Nov 2025 19:48:19 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1175718004D8 for ; Mon, 24 Nov 2025 19:48:18 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 6/7] nscd: Add __nscd_getgrnam and __nscd_getgrgid client functions In-Reply-To: Message-ID: References: X-From-Line: e20a4549ca6a3f463bc226fe5fa4fc9a89b62aa2 Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:48:16 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 1SGA_LA3nhKrf4graxaEDrgncJLsAzNlhGM69W7FoJA_1764013699 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org They allocate an appropriately sized result and do not need the ERANGE protocol. The current implementation still uses ERANGE internally, but the plan is to change that eventually. --- nscd/nscd_getgr_r.c | 52 ++++++++++++++++++++++++++++++++++++++++++++- nscd/nscd_proto.h | 2 ++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/nscd/nscd_getgr_r.c b/nscd/nscd_getgr_r.c index 173fadbd97..942eb8c2db 100644 --- a/nscd/nscd_getgr_r.c +++ b/nscd/nscd_getgr_r.c @@ -34,11 +34,13 @@ #include "nscd-client.h" #include "nscd_proto.h" +#include static int nscd_getgr_r (const char *key, size_t keylen, request_type type, struct group *resultbuf, char *buffer, size_t buflen, struct group **result); - +static int nscd_getgr (const char *key, size_t keylen, request_type type, + struct group **result); int __nscd_getgrnam_r (const char *name, struct group *resultbuf, char *buffer, @@ -48,6 +50,11 @@ __nscd_getgrnam_r (const char *name, struct group *resultbuf, char *buffer, buffer, buflen, result); } +int +__nscd_getgrnam (const char *name, struct group **result) +{ + return nscd_getgr (name, strlen (name) + 1, GETGRBYNAME, result); +} int __nscd_getgrgid_r (gid_t gid, struct group *resultbuf, char *buffer, @@ -61,6 +68,15 @@ __nscd_getgrgid_r (gid_t gid, struct group *resultbuf, char *buffer, buffer, buflen, result); } +int +__nscd_getgrgid (gid_t gid, struct group **result) +{ + char buf[3 * sizeof (gid_t)]; + buf[sizeof (buf) - 1] = '\0'; + char *cp = _itoa_word (gid, buf + sizeof (buf) - 1, 10, 0); + + return nscd_getgr (cp, buf + sizeof (buf) - cp, GETGRBYGID, result); +} libc_locked_map_ptr (,__gr_map_handle) attribute_hidden; /* Note that we only free the structure if necessary. The memory @@ -324,3 +340,37 @@ nscd_getgr_r (const char *key, size_t keylen, request_type type, return retval; } + +static int +nscd_getgr (const char *key, size_t keylen, request_type type, + struct group **result) +{ + struct scratch_buffer buf; + scratch_buffer_init (&buf); + + *result = NULL; + while (true) + { + struct group storage; + struct group *ptr = NULL; + int ret = nscd_getgr_r (key, keylen, type, &storage, + buf.data, buf.length, &ptr); + if (ret == ERANGE) + { + if (!scratch_buffer_grow (&buf)) + return ENOMEM; + } + else + { + if (ret == 0 && ptr != NULL) + { + ptr = __nss_generic_dup (nss_lookup_getgrgid, ptr); + if (ptr == NULL) + ret = ENOMEM; + *result = ptr; + } + scratch_buffer_free (&buf); + return ret; + } + } +} diff --git a/nscd/nscd_proto.h b/nscd/nscd_proto.h index 3b635620dc..db49a02b73 100644 --- a/nscd/nscd_proto.h +++ b/nscd/nscd_proto.h @@ -54,9 +54,11 @@ extern int __nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf, extern int __nscd_getgrnam_r (const char *name, struct group *resultbuf, char *buffer, size_t buflen, struct group **result) attribute_hidden; +int __nscd_getgrnam (const char *name, struct group **result) attribute_hidden; extern int __nscd_getgrgid_r (gid_t gid, struct group *resultbuf, char *buffer, size_t buflen, struct group **result) attribute_hidden; +int __nscd_getgrgid (gid_t, struct group **result) attribute_hidden; extern int __nscd_gethostbyname_r (const char *name, struct hostent *resultbuf, char *buffer, size_t buflen, From patchwork Mon Nov 24 19:48:22 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 125193 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 27ED13858406 for ; Mon, 24 Nov 2025 19:56:48 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 27ED13858406 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=JasxZZdR X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 73D713858408 for ; Mon, 24 Nov 2025 19:48:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 73D713858408 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 73D713858408 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013709; cv=none; b=c0Sw35LKK9kghkj03K8kGUrkctX+vjMoB5KAdA1HWr7OGO1VO5km3sigiI8AnKjGn1I1YIIERKTtCDM21WLO3yKvgX2/93VYGAfzLaDiibPJmSfKrc1uQspXkFPrBCZ6og2a2wrAsXOD7BcB2fMPLqs9/Qz/eyWmA4BFtRiNkzk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764013709; c=relaxed/simple; bh=4aktVNBLWdvqcjQtDtDi+Th44b4hLtP3nN2hyvLmJfM=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=VUCsAciVYB0x/m20r2DcLZWz7p5l+BGNpyrW8+BuHshTBKLd8A+n8YqeC5z6WD7gRU68dg37TZuBwWM+1eKegqZYK4Vfgu9lUvwNXZBUtYmBJTP1nHizj9Ia/O430XCqFgFmEdH672X3GEvtYpbMi+Rt+CVl3artL+z/SKo+EzI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 73D713858408 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764013709; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=beehDeHRuvlRfpEd4DG+majYKRYX5jyKbSitw2Vm2O8=; b=JasxZZdRxQhLpcaWyH+2K7/P+kp4B/mIQQXXRK/6ROBW4Z8RPgGfXNDZtf06JXPP6SSAwk NMks7tQe1elEXN2rpa2bn3qyhtFUpqf11ooFLalijEXgP0W5EGT/b3MzfEJSco14qY4c0Z O/4Ch480WVLnv/sPg9/oTyBzVa3D2hA= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-371-HW1M8K_hMVCtsl30A64CFw-1; Mon, 24 Nov 2025 14:48:26 -0500 X-MC-Unique: HW1M8K_hMVCtsl30A64CFw-1 X-Mimecast-MFC-AGG-ID: HW1M8K_hMVCtsl30A64CFw_1764013705 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id BF600180057A for ; Mon, 24 Nov 2025 19:48:25 +0000 (UTC) Received: from fweimer-oldenburg.csb.redhat.com (unknown [10.44.32.103]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B4A0D19560B2 for ; Mon, 24 Nov 2025 19:48:24 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 7/7] nss: Add new framework for hiding the ERANGE protocol internally In-Reply-To: Message-ID: References: X-From-Line: d1fe21987a2558ca2835ad517af90f394f52962a Mon Sep 17 00:00:00 2001 Date: Mon, 24 Nov 2025 20:48:22 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: Vx4DMT4vILqIHzKF5-zglwOatUmM8EZCgnGaiN8L594_1764013705 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org And a new implementation of group member list merging that implements member deduplication using a stringtable. It is necessary to update nss/tst-nss-test4 to reflect the deduplication of member lists. --- include/set-freeres.h | 4 +- malloc/set-freeres.c | 3 +- nss/Makefile | 11 +++ nss/getgrgid.c | 14 ++- nss/getgrgid_r.c | 20 ++--- nss/getgrnam.c | 14 ++- nss/getgrnam_r.c | 23 +++-- nss/nss_generic.h | 71 +++++++++++++++ nss/nss_generic_get.c | 61 +++++++++++++ nss/nss_generic_get_r.c | 39 +++++++++ nss/nss_generic_lookup.c | 51 +++++++++++ nss/nss_generic_next.c | 60 +++++++++++++ nss/nss_generic_nscd.c | 62 ++++++++++++++ nss/nss_generic_storage.h | 29 +++++++ nss/nss_getX.c | 53 ++++++++++++ nss/nss_getX_r.c | 45 ++++++++++ nss/nss_getXinfo.c | 64 ++++++++++++++ nss/nss_getgrXinfo.c | 176 ++++++++++++++++++++++++++++++++++++++ nss/tst-nss-test4.c | 12 +-- 19 files changed, 763 insertions(+), 49 deletions(-) create mode 100644 nss/nss_generic_get.c create mode 100644 nss/nss_generic_get_r.c create mode 100644 nss/nss_generic_lookup.c create mode 100644 nss/nss_generic_next.c create mode 100644 nss/nss_generic_nscd.c create mode 100644 nss/nss_generic_storage.h create mode 100644 nss/nss_getX.c create mode 100644 nss/nss_getX_r.c create mode 100644 nss/nss_getXinfo.c create mode 100644 nss/nss_getgrXinfo.c diff --git a/include/set-freeres.h b/include/set-freeres.h index 0fb25827ec..e64c4fd510 100644 --- a/include/set-freeres.h +++ b/include/set-freeres.h @@ -73,6 +73,8 @@ extern void __nss_module_freeres (void) attribute_hidden; extern void __nss_action_freeres (void) attribute_hidden; /* From nss/nss_database.c */ extern void __nss_database_freeres (void) attribute_hidden; +/* From nss/nss-generic.h, */ +void __nss_generic_freeres (void) attribute_hidden; /* From libio/genops.c */ extern int _IO_cleanup (void) attribute_hidden;; /* From dlfcn/dlerror.c */ @@ -107,8 +109,6 @@ extern printf_arginfo_size_function ** __libc_reg_printf_freemem_ptr extern printf_va_arg_function ** __libc_reg_type_freemem_ptr attribute_hidden; /* From nss/getXXbyYY.c */ -extern char * __libc_getgrgid_freemem_ptr attribute_hidden; -extern char * __libc_getgrnam_freemem_ptr attribute_hidden; extern char * __libc_getpwnam_freemem_ptr attribute_hidden; extern char * __libc_getpwuid_freemem_ptr attribute_hidden; extern char * __libc_getspnam_freemem_ptr attribute_hidden; diff --git a/malloc/set-freeres.c b/malloc/set-freeres.c index 2ac1df3c1c..740c726f30 100644 --- a/malloc/set-freeres.c +++ b/malloc/set-freeres.c @@ -131,6 +131,7 @@ __libc_freeres (void) call_function_static_weak (__nss_module_freeres); call_function_static_weak (__nss_action_freeres); call_function_static_weak (__nss_database_freeres); + call_function_static_weak (__nss_generic_freeres); _IO_cleanup (); @@ -205,8 +206,6 @@ __libc_freeres (void) call_free_static_weak (__libc_reg_printf_freemem_ptr); call_free_static_weak (__libc_reg_type_freemem_ptr); - call_free_static_weak (__libc_getgrgid_freemem_ptr); - call_free_static_weak (__libc_getgrnam_freemem_ptr); call_free_static_weak (__libc_getpwnam_freemem_ptr); call_free_static_weak (__libc_getpwuid_freemem_ptr); call_free_static_weak (__libc_getspnam_freemem_ptr); diff --git a/nss/Makefile b/nss/Makefile index 718c6f6b9d..53bc35719d 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -89,6 +89,17 @@ routines += \ getgrnam_r \ grp-merge \ initgroups \ + nss_generic_copy \ + nss_generic_dup \ + nss_generic_get \ + nss_generic_get_r \ + nss_generic_lookup \ + nss_generic_next \ + nss_generic_nscd \ + nss_getX \ + nss_getX_r \ + nss_getXinfo \ + nss_getgrXinfo \ nss_group_members \ putgrent \ # routines diff --git a/nss/getgrgid.c b/nss/getgrgid.c index e81b3bee9f..918bb83c0a 100644 --- a/nss/getgrgid.c +++ b/nss/getgrgid.c @@ -17,12 +17,10 @@ #include +#include -#define LOOKUP_TYPE struct group -#define FUNCTION_NAME getgrgid -#define DATABASE_NAME group -#define ADD_PARAMS gid_t gid -#define ADD_VARIABLES gid -#define BUFLEN NSS_BUFLEN_GROUP - -#include "../nss/getXXbyYY.c" +struct group * +getgrgid (gid_t gid) +{ + return __nss_getX (nss_lookup_getgrgid, &gid); +} diff --git a/nss/getgrgid_r.c b/nss/getgrgid_r.c index 12a8299749..72be20b07f 100644 --- a/nss/getgrgid_r.c +++ b/nss/getgrgid_r.c @@ -17,15 +17,13 @@ #include -#include +#include -#define LOOKUP_TYPE struct group -#define FUNCTION_NAME getgrgid -#define DATABASE_NAME group -#define ADD_PARAMS gid_t gid -#define ADD_VARIABLES gid -#define BUFLEN NSS_BUFLEN_GROUP -#define DEEPCOPY_FN __copy_grp -#define MERGE_FN __merge_grp - -#include +int getgrgid_r (gid_t gid, struct group *grp, + char *buffer, size_t length, struct group **result) +{ + void *ptr = grp; + int ret = __nss_getX_r (nss_lookup_getgrgid, &gid, &ptr, buffer, length); + *result = ptr; + return ret; +} diff --git a/nss/getgrnam.c b/nss/getgrnam.c index b24d564ea2..60f9e7472b 100644 --- a/nss/getgrnam.c +++ b/nss/getgrnam.c @@ -17,12 +17,10 @@ #include +#include -#define LOOKUP_TYPE struct group -#define FUNCTION_NAME getgrnam -#define DATABASE_NAME group -#define ADD_PARAMS const char *name -#define ADD_VARIABLES name -#define BUFLEN NSS_BUFLEN_GROUP - -#include "../nss/getXXbyYY.c" +struct group * +getgrnam (const char *name) +{ + return __nss_getX (nss_lookup_getgrnam, name); +} diff --git a/nss/getgrnam_r.c b/nss/getgrnam_r.c index 69b088f9c4..a64fdda7b1 100644 --- a/nss/getgrnam_r.c +++ b/nss/getgrnam_r.c @@ -17,15 +17,14 @@ #include -#include - -#define LOOKUP_TYPE struct group -#define FUNCTION_NAME getgrnam -#define DATABASE_NAME group -#define ADD_PARAMS const char *name -#define ADD_VARIABLES name - -#define DEEPCOPY_FN __copy_grp -#define MERGE_FN __merge_grp - -#include +#include + +int getgrnam_r(const char *name, struct group *grp, + char *buffer, size_t length, struct group **result) +{ + void *ptr = grp; + int ret = __nss_getX_r (nss_lookup_getgrnam, name, + &ptr, buffer, length); + *result = ptr; + return ret; +} diff --git a/nss/nss_generic.h b/nss/nss_generic.h index 820169ca12..9fbe785cce 100644 --- a/nss/nss_generic.h +++ b/nss/nss_generic.h @@ -58,4 +58,75 @@ int __nss_generic_copy (enum nss_lookup_type lt, const void *source, void *__nss_generic_dup (enum nss_lookup_type lt, const void *source) attribute_hidden; +/* Interpreted according to enum nss_lookup_type. Typically a string + or a pointer to an integer. */ +typedef const void *nss_lookup_key; + +/* Invokes the NSS module function SERVICE_FUNCTION of type LT using + KEY. The caller must initialization *RESULT to point to storage of + the appropriate NSS struct for LT. Upon return, the function + may write NULL to *RESULT and return with an error + (including triggering the ERANGE protocol), or leave *RESULT + unchanged and fill the struct with data, potentiallly using + LENGTH bytes at BUFFER for additional storage. */ +enum nss_status __nss_generic_get_r (enum nss_lookup_type lt, + nss_lookup_key key, + void *service_function, + void **result, + char *buffer, + size_t length) attribute_hidden; + +/* Like __nss_generic_get_buffer, but handles the ERANGE protocol. + *RESULT is not read, but overwritten with a malloc-allocated + pointer or NULL. */ +enum nss_status __nss_generic_get (enum nss_lookup_type lt, + nss_lookup_key key, + void *service_function, + void **result) attribute_hidden; + +_Bool __nss_generic_use_nscd (enum nss_lookup_type lt) attribute_hidden; +int __nss_generic_get_nscd (enum nss_lookup_type, nss_lookup_key, + void **result) attribute_hidden; + +/* Returns the pointer to the first service lookup function for lookup + type LT, or NULL if there are service modules. Updates *NIP + accordingly. */ +void *__nss_generic_lookup (enum nss_lookup_type lt, nss_action_list *ni) + attribute_hidden; + +/* See __nss_next2 in . The function names are selected + based on the type LT. */ +int __nss_generic_next (enum nss_lookup_type lt, nss_action_list *ni, + void *fctp, int status, int all_values) + attribute_hidden; + +/* Perform a group lookup with merging. On success, return zero and + write a malloc-allocated struct group pointer to *RESULT (positive + result) or NULL (negative result). On failure, return -1 and write + NULL to *RESULT, and set errno accordingly. */ +int __nss_getXinfo (enum nss_lookup_type lt, + nss_lookup_key key, void **result) attribute_hidden; + +/* Like __nss_getXinfo, but LT must be nss_lookup_getgrgid or + nss_lookup_getgrnam. This function does not contact nscd and is + used in the implementation of __nss_getXInfo. */ +int __nss_getgrXinfo (enum nss_lookup_type lt, + nss_lookup_key key, void **result) attribute_hidden; + +/* Implementation of the non-_r public interface. RESULT must be the + address of the global variable. It is freed before the lookup + starts. Returns NULL on failure, and a pointer to the NSS struct + appropriate for LT on success. Negative lookup returns NULL and + does not set errno. Other errors set errno. */ +void *__nss_getX (enum nss_lookup_type lt, nss_lookup_key key) + attribute_hidden; + +/* Implementation of the _r public interface. The caller must + initialize *RESULT with the address of the result structure passed + to the _r function. Return 0 on success and error code on + failure (including ERANGE). */ +int __nss_getX_r (enum nss_lookup_type lt, nss_lookup_key key, + void **result, char *buffer, size_t length) + attribute_hidden; + #endif /* NSS_GENERIC */ diff --git a/nss/nss_generic_get.c b/nss/nss_generic_get.c new file mode 100644 index 0000000000..88dc99a143 --- /dev/null +++ b/nss/nss_generic_get.c @@ -0,0 +1,61 @@ +/* Implementation of the ERANGE protocol for service module functions. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +enum nss_status +__nss_generic_get (enum nss_lookup_type lt, + nss_lookup_key key, + void *service_function, + void **result) +{ + struct scratch_buffer buf; + scratch_buffer_init (&buf); + + *result = NULL; + while (true) + { + union nss_generic_storage storage; + void *ptr = &storage; + enum nss_status status = __nss_generic_get_r (lt, key, + service_function, &ptr, + buf.data, buf.length); + if (status == NSS_STATUS_TRYAGAIN && errno == ERANGE) + { + if (!scratch_buffer_grow (&buf)) + return status; + } + else + { + if (status == NSS_STATUS_SUCCESS) + { + if (ptr != NULL) + { + ptr = __nss_generic_dup (lt, ptr); + if (ptr == NULL) + status = NSS_STATUS_TRYAGAIN; + *result = ptr; + } + } + scratch_buffer_free (&buf); + return status; + } + } +} diff --git a/nss/nss_generic_get_r.c b/nss/nss_generic_get_r.c new file mode 100644 index 0000000000..768a55f154 --- /dev/null +++ b/nss/nss_generic_get_r.c @@ -0,0 +1,39 @@ +/* Lookup-indendent call to an NSS service function. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include + +enum nss_status +__nss_generic_get_r (enum nss_lookup_type lt, + nss_lookup_key key, + void *service_function, + void **result, + char *buffer, + size_t length) +{ + switch (lt) + { + case nss_lookup_getgrgid: + return ((nss_getgrgid_r *) service_function) + (*(const gid_t *) key, *result, buffer, length, &errno); + case nss_lookup_getgrnam: + return ((nss_getgrnam_r *) service_function) + (key, *result, buffer, length, &errno); + } + __builtin_unreachable (); +} diff --git a/nss/nss_generic_lookup.c b/nss/nss_generic_lookup.c new file mode 100644 index 0000000000..428144ddfd --- /dev/null +++ b/nss/nss_generic_lookup.c @@ -0,0 +1,51 @@ +/* Type-generic lookup of the first NSS service module function. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +void * +__nss_generic_lookup (enum nss_lookup_type lt, nss_action_list *ni) +{ + int database; + const char *fct_name; + const char *fct2_name = NULL; + + switch (lt) + { +#define DEFINE_LOOKUP(name, dbname, function) \ + case nss_lookup_##name: \ + database = NSS_DBSIDX_##dbname; \ + fct_name = function; \ + break; +#include +#undef DEFINE_LOOKUP + default: + abort (); + } + + if (! __nss_database_get (database, ni)) + return NULL; + + assert (*ni != NULL); + void *fct; + if (__nss_lookup (ni, fct_name, fct2_name, &fct) == 0) + return fct; + else + return NULL; +} diff --git a/nss/nss_generic_next.c b/nss/nss_generic_next.c new file mode 100644 index 0000000000..c0a6fb98c5 --- /dev/null +++ b/nss/nss_generic_next.c @@ -0,0 +1,60 @@ +/* Type-generic next NSS service module function lookup. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +int +__nss_generic_next (enum nss_lookup_type lt, nss_action_list *ni, + void *fctp, int status, int all_values) +{ + /* Extract the database index and function name from . */ + int database; + const char *fct_name; + + switch (lt) + { +#define DEFINE_LOOKUP(name, dbname, function) \ + case nss_lookup_##name: \ + database = NSS_DBSIDX_##dbname; \ + fct_name = function; \ + break; +#include +#undef DEFINE_LOOKUP + default: + abort (); + } + + /* No secondary functions yet. */ + const char *fct2_name = NULL; + + int ret; + if (*ni == NULL) + { + /* First call. Ignore status and all_values. */ + if (! __nss_database_get (database, ni)) + return -1; + + assert (*ni != NULL); + ret = __nss_lookup (ni, fct_name, fct2_name, fctp); + } + else + ret = __nss_next2 (ni, fct_name, fct2_name, fctp, status, all_values); + + return ret; +} diff --git a/nss/nss_generic_nscd.c b/nss/nss_generic_nscd.c new file mode 100644 index 0000000000..54306290e7 --- /dev/null +++ b/nss/nss_generic_nscd.c @@ -0,0 +1,62 @@ +/* Generic malloc-compatible version of nscd get functions. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +_Bool +__nss_generic_use_nscd (enum nss_lookup_type lt) +{ + switch (lt) + { +#define DEFINE_LOOKUP(name, dbname, function) \ + case nss_lookup_##name: \ + return __nscd_use_database (NSS_DBSIDX_##dbname); +#include +#undef DEFINE_LOOKUP + } + __builtin_unreachable (); +} + +int +__nss_generic_get_nscd (enum nss_lookup_type lt, nss_lookup_key key, + void **result) +{ + if (!__nss_generic_use_nscd (lt)) + return -1; /* Use NSS modules. */ + switch (lt) + { + case nss_lookup_getgrgid: + { + struct group *ptr; + int ret = __nscd_getgrgid (*(const gid_t *) key, &ptr); + *result = ptr; + return ret; + } + case nss_lookup_getgrnam: + { + struct group *ptr; + int ret = __nscd_getgrnam (key, &ptr); + *result = ptr; + return ret; + } + } + + __builtin_unreachable (); +} diff --git a/nss/nss_generic_storage.h b/nss/nss_generic_storage.h new file mode 100644 index 0000000000..f91cef36e5 --- /dev/null +++ b/nss/nss_generic_storage.h @@ -0,0 +1,29 @@ +/* Type that can hold all NSS structs. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef NSS_GENERIC_STORAGE +#define NSS_GENERIC_STORAGE + +#include + +union nss_generic_storage +{ + struct group grp; +}; + +#endif /* NSS_GENERIC_STORAGE */ diff --git a/nss/nss_getX.c b/nss/nss_getX.c new file mode 100644 index 0000000000..4727400e31 --- /dev/null +++ b/nss/nss_getX.c @@ -0,0 +1,53 @@ +/* Type-generic implementation of public non-_r NSS legacy functions. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include + +static void *__nss_generic_allocation[nss_lookup_MAX]; +static __libc_lock_t __nss_generic_lock[nss_lookup_MAX]; + +void * +__nss_getX (enum nss_lookup_type lt, nss_lookup_key key) +{ + __libc_lock_lock (__nss_generic_lock[lt]); + free (__nss_generic_allocation[lt]); + __nss_generic_allocation[lt] = NULL; + + /* Preserve errno (possibly 0) if ret == 0. */ + int saved_errno = errno; + void *result; + int ret = __nss_getXinfo (lt, key, &result); + __nss_generic_allocation[lt] = result; + + __libc_lock_unlock (__nss_generic_lock[lt]); + if (ret == 0) + __set_errno (saved_errno); + + return result; +} + +void +__nss_generic_freeres (void) +{ + for (int i = 0; i < array_length (__nss_generic_allocation); ++i) + free (__nss_generic_allocation[i]); +} diff --git a/nss/nss_getX_r.c b/nss/nss_getX_r.c new file mode 100644 index 0000000000..2de2975cae --- /dev/null +++ b/nss/nss_getX_r.c @@ -0,0 +1,45 @@ +/* Type-generic implementation of public NSS get*_r functions. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +int +__nss_getX_r (enum nss_lookup_type lt, nss_lookup_key key, + void **result, char *buffer, size_t length) +{ + void *storage = *result; + *result = NULL; + + void *allocated; + int ret = __nss_getXinfo (lt, key, &allocated); + if (ret == 0) + { + if (allocated != NULL) + { + ret = __nss_generic_copy (lt, allocated, storage, buffer, length); + if (ret == 0) + *result = storage; + free (allocated); + } + return ret; + } + else + return errno; +} diff --git a/nss/nss_getXinfo.c b/nss/nss_getXinfo.c new file mode 100644 index 0000000000..9ba0401b8b --- /dev/null +++ b/nss/nss_getXinfo.c @@ -0,0 +1,64 @@ +/* Generic malloc-compatible version of NSS get functions. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include + +int +__nss_getXinfo (enum nss_lookup_type lt, + nss_lookup_key key, void **result) +{ +#if USE_NSCD + if (__nss_generic_use_nscd (lt) + && __nss_generic_get_nscd (lt, key, result) == 0) + return 0; +#endif + + switch (lt) + { + case nss_lookup_getgrgid: + case nss_lookup_getgrnam: + /* Group lookups are handled separately to implement merging. */ + return __nss_getgrXinfo (lt, key, result); + default: + break; + } + + nss_action_list nip = NULL; + void *fct; + int no_more = __nss_generic_next (lt, &nip, &fct, 0, 0); + enum nss_status status = NSS_STATUS_UNAVAIL; + + *result = NULL; + while (no_more == 0) + { + void *ptr; + status = __nss_generic_get (lt, key, fct, &ptr); + if (status == NSS_STATUS_SUCCESS) + { + free (*result); + *result = ptr; + } + + no_more = __nss_generic_next (lt, &nip, &fct, status, 0); + } + + if (status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND) + return 0; + else + return errno; +} diff --git a/nss/nss_getgrXinfo.c b/nss/nss_getgrXinfo.c new file mode 100644 index 0000000000..6bac0d64e6 --- /dev/null +++ b/nss/nss_getgrXinfo.c @@ -0,0 +1,176 @@ +/* Implementation of the getgrXinfo family of functions. + Copyright (C) 1996-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline bool +__nss_status_has_result (enum nss_status status) +{ + return status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND; +} + +static int +__nss_status_to_posix_result (enum nss_status status) +{ + /* Treat NSS_STATUS_UNAVAIL like NSS_STATUS_NOTFOUND. Some NSS + modules return NSS_STATUS_UNAVAIL if they have disabled + themselves. */ + if (status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND + || NSS_STATUS_UNAVAIL) + return 0; + else + { + /* Paranoia, to avoid endless loops. */ + if (errno == ERANGE) + __set_errno (EINVAL); + return -1; + } +} + +int +__nss_getgrXinfo (enum nss_lookup_type lt, + nss_lookup_key key, + void **result) +{ + /* Default if no service modules are available. */ + enum nss_status status = NSS_STATUS_UNAVAIL; + *result = NULL; + + /* For merging. If table is not empty, then it supersedes the group + members in first_result. */ + struct group *first_result = NULL; + struct nss_group_members table = { }; + __nss_group_members_init (&table); + + /* With better error reporting (especially from dlopen), we could + check for NSS initialization errors here and report them. */ + nss_action_list nip; + void *fct = __nss_generic_lookup (lt, &nip); + + int no_more = fct == NULL; + bool do_merge = false; + while (no_more == 0) + { + void *ptr; + enum nss_status status = __nss_generic_get (lt, key, fct, &ptr); + struct group *grp = ptr; + + if (status == NSS_STATUS_SUCCESS) + { + assert (grp != NULL); + /* We have result data. It may need to be merged. We only + support merging members of groups with identical names + and GID values. If we hit this, the grp result overrides + the first result. */ + if (do_merge && first_result != NULL + && grp->gr_gid == first_result->gr_gid + && strcmp (grp->gr_name, first_result->gr_name) == 0) + { + /* Perform the merge. If no members have been merged yet, + process first_result as well. */ + if ((table.T.count == 0 + && !__nss_group_members_add (&table, first_result)) + || !__nss_group_members_add (&table, grp)) + { + __nss_group_members_free (&table); + free (grp); + free (first_result); + return -1; + } + free (grp); + } + else + { + /* No merge or different data. New result replaces + previous result. */ + free (first_result); + first_result = grp; + __nss_group_members_free (&table); + } + } + else if (status == NSS_STATUS_TRYAGAIN) + { + free (first_result); + __nss_group_members_free (&table); + return -1; + } + else if (first_result != NULL) + { + /* If the result wasn't SUCCESS, use the stored data in + first_result/table and set the status back to + NSS_STATUS_SUCCESS to match the previous pass through + the loop. + + * If the next action is CONTINUE, it will overwrite the value + currently in the buffer and return the new value. + * If the next action is RETURN, we'll return the previously- + acquired values. + * If the next action is MERGE, then it will be added to the + buffer saved from the previous source. */ + status = NSS_STATUS_SUCCESS; + } + + do_merge = (nss_next_action (nip, status) == NSS_ACTION_MERGE + && status == NSS_STATUS_SUCCESS); + no_more = __nss_generic_next (lt, &nip, &fct, status, 0); + } + + + if (table.T.count > 0) + { + /* We have something to merge. */ + struct group *merged_allocated; + { + struct group merged = *first_result; + merged.gr_mem = __nss_group_members (&table); + if (merged.gr_mem == NULL) + { + __nss_group_members_free (&table); + free (first_result); + return -1; + } + + /* Make a consolidated copy of the entire group information. */ + merged_allocated = __nss_generic_dup (lt, &merged); + + free (merged.gr_mem); + } + + __nss_group_members_free (&table); + free (first_result); + first_result = merged_allocated; + + if (first_result == NULL) + return -1; + } + else + /* No merging necessary. We can use first_result. */ + __nss_group_members_free (&table); + + *result = first_result; + return __nss_status_to_posix_result (status); +} diff --git a/nss/tst-nss-test4.c b/nss/tst-nss-test4.c index 3d94c12d08..c2c0d3f5a1 100644 --- a/nss/tst-nss-test4.c +++ b/nss/tst-nss-test4.c @@ -100,9 +100,7 @@ do_test (void) /* At least 3 service modules are needed to reproduce BZ#33361. */ __nss_configure_lookup ("group", "test1 [SUCCESS=merge] test2 files"); - /* Test increasing sizes of group_2 to see if we fail, starting with - member_cnt == 1 to ensure we always check for no de-duplication - e.g. { "foo", NULL } */ + /* Test increasing sizes of group_2 to see if we fail. */ for (member_cnt = 1; member_cnt < array_length (group_2); member_cnt++) { verbose_printf ("Outer loop - member_cnt is %d\n", member_cnt); @@ -127,15 +125,17 @@ do_test (void) verbose_printf ("MERGED LIST of [%d] is %s\n", i, merge_1[i]); } - /* Add group_2 to the merge list */ - int group2_index = 0; + /* Add group_2 to the merge list. Skip the duplicate "foo" + group at the start. */ + int group2_index = 1; for (i = array_length (group_1) - 1; i < array_length (group_1) - 1 + member_cnt; i++) { merge_1[i] = xasprintf ("%s", group_2[group2_index++]); verbose_printf ("MERGED LIST of [%d] is %s\n", i, merge_1[i]); } - merge_1[array_length(group_1) - 1 + member_cnt]= NULL; + /* Skipping the "foo" group reduced the member count by 1. */ + merge_1[array_length(group_1) - 1 + member_cnt - 1]= NULL; align_mask = __alignof__ (struct group) - 1; align_mem_mask = __alignof__ (char *) - 1;