[PATCH 7/7] nss: Add new framework for hiding the ERANGE protocol internally

Florian Weimer fweimer@redhat.com
Mon Nov 24 19:48:22 GMT 2025


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 <grp.h>
 
+#include <nss_generic.h>
 
-#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 <grp.h>
 
-#include <grp-merge.h>
+#include <nss_generic.h>
 
-#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 <nss/getXXbyYY_r.c>
+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 <grp.h>
 
+#include <nss_generic.h>
 
-#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 <grp.h>
 
-#include <grp-merge.h>
-
-#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 <nss/getXXbyYY_r.c>
+#include <nss_generic.h>
+
+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 <nsswitch.h>.  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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <nss_generic.h>
+#include <nss_generic_storage.h>
+#include <scratch_buffer.h>
+
+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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <nss_generic.h>
+
+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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <nss_generic.h>
+
+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 <nss-lookups.def>
+#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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <nss_generic.h>
+
+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 <nss-lookups.def>.  */
+  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 <nss-lookups.def>
+#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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <nscd/nscd_proto.h>
+#include <nss_generic.h>
+#include <grp.h>
+
+_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 <nss-lookups.def>
+#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
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef NSS_GENERIC_STORAGE
+#define NSS_GENERIC_STORAGE
+
+#include <grp.h>
+
+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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <libc-lock.h>
+#include <nss_generic.h>
+#include <set-freeres.h>
+#include <stdlib.h>
+
+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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <nss_generic.h>
+#include <stdlib.h>
+
+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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <nss_generic.h>
+
+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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <errno.h>
+#include <grp.h>
+#include <nss_generic.h>
+#include <nss.h>
+#include <nss_group_members.h>
+#include <nsswitch.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stringtable.h>
+
+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;
-- 
2.52.0



More information about the Libc-alpha mailing list