[v2,20/23] nss: Convert passwd database to new NSS framework

Message ID 162212a5d39eff5a369280ec19116f6abeac0c37.1774037705.git.fweimer@redhat.com (mailing list archive)
State Failed CI
Headers
Series NSS, nscd updates (for group merging and more) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed

Commit Message

Florian Weimer March 20, 2026, 8:43 p.m. UTC
  This framework moves the ERANGE down a couple of layers.  The intent
is to migrate to an interface eventually where NSS service modules
allocate data for the response size they need.

The need for the function-specific GLIBC_2.1.2 versioned symbol
is rather unfortunate.  With out the existing preprocessor approach
in nss/getXXbyYY_r.c, this adds a lot of duplicated code.  A future
version could use compile-time code generation (outside of the
preprocessor) to generate the _r function wrappers and the
code for the compatibility code.  The advantage over the preprocessor
approach is that the resulting code will still be straightforward
to debug because line-based breakpoints work exactly as expected.
---
 include/set-freeres.h     |  4 +--
 malloc/set-freeres.c      |  7 +---
 nscd/nscd_proto.h         |  2 ++
 nss/Makefile              |  7 ++++
 nss/getpwnam.c            | 14 ++++----
 nss/getpwnam_r.c          | 33 +++++++++++++++----
 nss/getpwuid.c            | 14 ++++----
 nss/getpwuid_r.c          | 33 +++++++++++++++----
 nss/nss_generic.h         | 63 ++++++++++++++++++++++++++++++++++++
 nss/nss_generic_get.c     | 61 +++++++++++++++++++++++++++++++++++
 nss/nss_generic_get_r.c   | 45 ++++++++++++++++++++++++++
 nss/nss_generic_lookup.c  | 51 +++++++++++++++++++++++++++++
 nss/nss_generic_next.c    | 60 +++++++++++++++++++++++++++++++++++
 nss/nss_generic_storage.h | 30 ++++++++++++++++++
 nss/nss_getX.c            | 53 +++++++++++++++++++++++++++++++
 nss/nss_getX_r.c          | 45 ++++++++++++++++++++++++++
 nss/nss_getXinfo.c        | 67 +++++++++++++++++++++++++++++++++++++++
 17 files changed, 551 insertions(+), 38 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_storage.h
 create mode 100644 nss/nss_getX.c
 create mode 100644 nss/nss_getX_r.c
 create mode 100644 nss/nss_getXinfo.c
  

Comments

Carlos O'Donell March 24, 2026, 9:39 p.m. UTC | #1
On 3/20/26 4:43 PM, Florian Weimer wrote:
> This framework moves the ERANGE down a couple of layers.  The intent
> is to migrate to an interface eventually where NSS service modules
> allocate data for the response size they need.
> 
> The need for the function-specific GLIBC_2.1.2 versioned symbol
> is rather unfortunate.  With out the existing preprocessor approach
> in nss/getXXbyYY_r.c, this adds a lot of duplicated code.  A future
> version could use compile-time code generation (outside of the
> preprocessor) to generate the _r function wrappers and the
> code for the compatibility code.  The advantage over the preprocessor
> approach is that the resulting code will still be straightforward
> to debug because line-based breakpoints work exactly as expected.

LGTM.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> ---
>   include/set-freeres.h     |  4 +--
>   malloc/set-freeres.c      |  7 +---
>   nscd/nscd_proto.h         |  2 ++
>   nss/Makefile              |  7 ++++
>   nss/getpwnam.c            | 14 ++++----
>   nss/getpwnam_r.c          | 33 +++++++++++++++----
>   nss/getpwuid.c            | 14 ++++----
>   nss/getpwuid_r.c          | 33 +++++++++++++++----
>   nss/nss_generic.h         | 63 ++++++++++++++++++++++++++++++++++++
>   nss/nss_generic_get.c     | 61 +++++++++++++++++++++++++++++++++++
>   nss/nss_generic_get_r.c   | 45 ++++++++++++++++++++++++++
>   nss/nss_generic_lookup.c  | 51 +++++++++++++++++++++++++++++
>   nss/nss_generic_next.c    | 60 +++++++++++++++++++++++++++++++++++
>   nss/nss_generic_storage.h | 30 ++++++++++++++++++
>   nss/nss_getX.c            | 53 +++++++++++++++++++++++++++++++
>   nss/nss_getX_r.c          | 45 ++++++++++++++++++++++++++
>   nss/nss_getXinfo.c        | 67 +++++++++++++++++++++++++++++++++++++++
>   17 files changed, 551 insertions(+), 38 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_storage.h
>   create mode 100644 nss/nss_getX.c
>   create mode 100644 nss/nss_getX_r.c
>   create mode 100644 nss/nss_getXinfo.c
> 
> diff --git a/include/set-freeres.h b/include/set-freeres.h
> index 55093aa89d..c9d2c8ae55 100644
> --- a/include/set-freeres.h
> +++ b/include/set-freeres.h
> @@ -68,6 +68,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 */
> @@ -102,8 +104,6 @@ extern printf_va_arg_function ** __libc_reg_type_freemem_ptr
>   /* 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;
>   extern char * __libc_getaliasbyname_freemem_ptr attribute_hidden;
>   extern char * __libc_gethostbyaddr_freemem_ptr attribute_hidden;
> diff --git a/malloc/set-freeres.c b/malloc/set-freeres.c
> index 39fd7d4d27..8112a2f664 100644
> --- a/malloc/set-freeres.c
> +++ b/malloc/set-freeres.c
> @@ -74,8 +74,6 @@
>   # pragma weak __libc_reg_type_freemem_ptr
>   # pragma weak __libc_getgrgid_freemem_ptr
>   # pragma weak __libc_getgrnam_freemem_ptr
> -# pragma weak __libc_getpwnam_freemem_ptr
> -# pragma weak __libc_getpwuid_freemem_ptr
>   # pragma weak __libc_getspnam_freemem_ptr
>   # pragma weak __libc_getaliasbyname_freemem_ptr
>   # pragma weak __libc_gethostbyaddr_freemem_ptr
> @@ -124,6 +122,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 ();
>   
> @@ -188,10 +187,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);
>         call_free_static_weak (__libc_getaliasbyname_freemem_ptr);
>         call_free_static_weak (__libc_gethostbyaddr_freemem_ptr);
> diff --git a/nscd/nscd_proto.h b/nscd/nscd_proto.h
> index 6c4a318bc0..da3156b0ff 100644
> --- a/nscd/nscd_proto.h
> +++ b/nscd/nscd_proto.h
> @@ -42,9 +42,11 @@ void __nscd_disable_database (unsigned int db) attribute_hidden;
>   extern int __nscd_getpwnam_r (const char *name, struct passwd *resultbuf,
>   			      char *buffer, size_t buflen,
>   			      struct passwd **result) attribute_hidden;
> +int __nscd_getpwnam (const char *name, struct passwd **) attribute_hidden;
>   extern int __nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf,
>   			      char *buffer,  size_t buflen,
>   			      struct passwd **result) attribute_hidden;
> +int __nscd_getpwuid (uid_t uid, struct passwd **result) attribute_hidden;
>   extern int __nscd_getgrnam_r (const char *name, struct group *resultbuf,
>   			      char *buffer, size_t buflen,
>   			      struct group **result) attribute_hidden;
> diff --git a/nss/Makefile b/nss/Makefile
> index 82f5a5d68f..a13f6fa10c 100644
> --- a/nss/Makefile
> +++ b/nss/Makefile
> @@ -47,7 +47,14 @@ routines = \
>     nss_files_functions \
>     nss_generic_copy \
>     nss_generic_dup \
> +  nss_generic_get \
> +  nss_generic_get_r \
> +  nss_generic_lookup \
> +  nss_generic_next \

OK.

>     nss_generic_nscd \
> +  nss_getX \
> +  nss_getX_r \
> +  nss_getXinfo \

OK.

>     nss_hash \
>     nss_module \
>     nss_parse_line_result \
> diff --git a/nss/getpwnam.c b/nss/getpwnam.c
> index 36d5a43fd6..55578f7fc0 100644
> --- a/nss/getpwnam.c
> +++ b/nss/getpwnam.c
> @@ -17,12 +17,10 @@
>   
>   #include <pwd.h>
>   
> +#include <nss_generic.h>
>   
> -#define LOOKUP_TYPE	struct passwd
> -#define FUNCTION_NAME	getpwnam
> -#define DATABASE_NAME	passwd
> -#define ADD_PARAMS	const char *name
> -#define ADD_VARIABLES	name
> -#define BUFLEN		NSS_BUFLEN_PASSWD
> -
> -#include "../nss/getXXbyYY.c"
> +struct passwd *
> +getpwnam (const char *name)
> +{
> +  return __nss_getX (nss_lookup_getpwnam, name);
> +}
> diff --git a/nss/getpwnam_r.c b/nss/getpwnam_r.c
> index 1ba6ba53c9..2bb0bfa5e1 100644
> --- a/nss/getpwnam_r.c
> +++ b/nss/getpwnam_r.c
> @@ -17,12 +17,31 @@
>   
>   #include <pwd.h>
>   
> +#include <nss_generic.h>
> +#include <shlib-compat.h>
>   
> -#define LOOKUP_TYPE	struct passwd
> -#define FUNCTION_NAME	getpwnam
> -#define DATABASE_NAME	passwd
> -#define ADD_PARAMS	const char *name
> -#define ADD_VARIABLES	name
> -#define BUFLEN		NSS_BUFLEN_PASSWD
> +int
> +___getpwnam_r (const char *name, struct passwd *pwd,
> +	      char *buffer, size_t length, struct passwd **result)
> +{
> +  void *ptr = pwd;
> +  int ret = __nss_getX_r (nss_lookup_getpwnam, name, &ptr, buffer, length);
> +  *result = ptr;
> +  return ret;
> +}
> +strong_alias (___getpwnam_r, __getpwnam_r)
> +versioned_symbol (libc, ___getpwnam_r, getpwnam_r, GLIBC_2_1_2);
>   
> -#include <nss/getXXbyYY_r.c>
> +#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1_2)
> +int
> +attribute_compat_text_section
> +__old_getpwnam_r (const char *name, struct passwd *pwd,
> +		   char *buffer, size_t length, struct passwd **result)
> +{
> +  int ret = __getpwnam_r (name, pwd, buffer, length, result);
> +  if (ret != 0 || result == NULL)
> +    ret = -1;
> +  return ret;
> +}
> +compat_symbol (libc, __old_getpwnam_r, getpwnam_r, GLIBC_2_0);
> +#endif
> diff --git a/nss/getpwuid.c b/nss/getpwuid.c
> index 9002dbbc34..96cccdcd89 100644
> --- a/nss/getpwuid.c
> +++ b/nss/getpwuid.c
> @@ -17,12 +17,10 @@
>   
>   #include <pwd.h>
>   
> +#include <nss_generic.h>
>   
> -#define LOOKUP_TYPE	struct passwd
> -#define FUNCTION_NAME	getpwuid
> -#define DATABASE_NAME	passwd
> -#define ADD_PARAMS	uid_t uid
> -#define ADD_VARIABLES	uid
> -#define BUFLEN		NSS_BUFLEN_PASSWD
> -
> -#include "../nss/getXXbyYY.c"
> +struct passwd *
> +getpwuid (uid_t uid)
> +{
> +  return __nss_getX (nss_lookup_getpwuid, &uid);
> +}
> diff --git a/nss/getpwuid_r.c b/nss/getpwuid_r.c
> index 7c6f3970fa..d99846bb57 100644
> --- a/nss/getpwuid_r.c
> +++ b/nss/getpwuid_r.c
> @@ -17,12 +17,31 @@
>   
>   #include <pwd.h>
>   
> +#include <nss_generic.h>
> +#include <shlib-compat.h>
>   
> -#define LOOKUP_TYPE	struct passwd
> -#define FUNCTION_NAME	getpwuid
> -#define DATABASE_NAME	passwd
> -#define ADD_PARAMS	uid_t uid
> -#define ADD_VARIABLES	uid
> -#define BUFLEN		NSS_BUFLEN_PASSWD
> +int
> +___getpwuid_r (uid_t uid, struct passwd *pwd,
> +              char *buffer, size_t length, struct passwd **result)
> +{
> +  void *ptr = pwd;
> +  int ret = __nss_getX_r (nss_lookup_getpwuid, &uid, &ptr, buffer, length);
> +  *result = ptr;
> +  return ret;
> +}
> +strong_alias (___getpwuid_r, __getpwuid_r)
> +versioned_symbol (libc, ___getpwuid_r, getpwuid_r, GLIBC_2_1_2);
>   
> -#include <nss/getXXbyYY_r.c>
> +#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1_2)
> +int
> +attribute_compat_text_section
> +__old_getpwuid_r (uid_t uid, struct passwd *pwd,
> +                   char *buffer, size_t length, struct passwd **result)
> +{
> +  int ret = __getpwuid_r (uid, pwd, buffer, length, result);
> +  if (ret != 0 || result == NULL)
> +    ret = -1;
> +  return ret;
> +}
> +compat_symbol (libc, __old_getpwuid_r, getpwuid_r, GLIBC_2_0);
> +#endif
> diff --git a/nss/nss_generic.h b/nss/nss_generic.h
> index 8a5b2cb896..107ebb2003 100644
> --- a/nss/nss_generic.h
> +++ b/nss/nss_generic.h
> @@ -80,4 +80,67 @@ typedef const void *nss_lookup_key;
>   bool __nscd_generic_get (enum nss_lookup_type lt, nss_lookup_key key,
>                            void **result);
>   
> +/* Invokes the NSS module function SERVICE_FUNCTION of type LT using
> +   KEY.  The caller must initialize *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, potentially 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_r, 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;
> +
> +/* Returns the pointer to the first service lookup function for lookup
> +   type LT, or NULL if there are no 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_H */
> diff --git a/nss/nss_generic_get.c b/nss/nss_generic_get.c
> new file mode 100644
> index 0000000000..3319a2b9c0
> --- /dev/null
> +++ b/nss/nss_generic_get.c
> @@ -0,0 +1,61 @@
> +/* Implementation of the ERANGE protocol for service module functions.
> +   Copyright (C) 2026 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..12fedee10f
> --- /dev/null
> +++ b/nss/nss_generic_get_r.c
> @@ -0,0 +1,45 @@
> +/* Lookup-independent call to an NSS service function.
> +   Copyright (C) 2026 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);
> +      case nss_lookup_getpwnam:
> +        return ((nss_getpwnam_r *) service_function)
> +          (key, *result, buffer, length, &errno);
> +      case nss_lookup_getpwuid:
> +        return ((nss_getpwuid_r *) service_function)
> +          (*(const uid_t *) 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..f9e41311bc
> --- /dev/null
> +++ b/nss/nss_generic_lookup.c
> @@ -0,0 +1,51 @@
> +/* Type-generic lookup of the first NSS service module function.
> +   Copyright (C) 2026 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, nscddb)   \
> +      case nss_lookup_##name:                           \
> +        database = nss_database_##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..abc5a61e11
> --- /dev/null
> +++ b/nss/nss_generic_next.c
> @@ -0,0 +1,60 @@
> +/* Type-generic next NSS service module function lookup.
> +   Copyright (C) 2026 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, nscddb)   \
> +      case nss_lookup_##name:                           \
> +        database = nss_database_##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_storage.h b/nss/nss_generic_storage.h
> new file mode 100644
> index 0000000000..334f51be70
> --- /dev/null
> +++ b/nss/nss_generic_storage.h
> @@ -0,0 +1,30 @@
> +/* Type that can hold all NSS structs.
> +   Copyright (C) 2026 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>
> +#include <pwd.h>
> +
> +union nss_generic_storage
> +{
> +  struct passwd pwd;
> +};
> +
> +#endif /* NSS_GENERIC_STORAGE */
> diff --git a/nss/nss_getX.c b/nss/nss_getX.c
> new file mode 100644
> index 0000000000..8595e5e3b0
> --- /dev/null
> +++ b/nss/nss_getX.c
> @@ -0,0 +1,53 @@
> +/* Type-generic implementation of public non-_r NSS legacy functions.
> +   Copyright (C) 2026 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..b0d488c889
> --- /dev/null
> +++ b/nss/nss_getX_r.c
> @@ -0,0 +1,45 @@
> +/* Type-generic implementation of public NSS get*_r functions.
> +   Copyright (C) 2026 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..0b2588c9fd
> --- /dev/null
> +++ b/nss/nss_getXinfo.c
> @@ -0,0 +1,67 @@
> +/* Generic malloc-compatible version of NSS get functions.
> +   Copyright (C) 2026 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)
> +{
> +#ifdef USE_NSCD
> +  if (__nscd_generic_get (lt, key, result))
> +    return 0;
> +#endif
> +
> +  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;
> +        }
> +
> +      /* MERGE is invalid for most databases.  */
> +      if ((nss_next_action (nip, status) == NSS_ACTION_MERGE
> +           && status == NSS_STATUS_SUCCESS))
> +        {
> +          __set_errno (EINVAL);
> +          status = NSS_STATUS_UNAVAIL;
> +          break;
> +        }
> +
> +      no_more = __nss_generic_next (lt, &nip, &fct, status, 0);
> +    }
> +
> +  if (status != NSS_STATUS_SUCCESS)
> +    {
> +      free (*result);
> +      *result = NULL;
> +    }
> +
> +  if (status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND)
> +    return 0;
> +  else
> +    return errno;
> +}
  

Patch

diff --git a/include/set-freeres.h b/include/set-freeres.h
index 55093aa89d..c9d2c8ae55 100644
--- a/include/set-freeres.h
+++ b/include/set-freeres.h
@@ -68,6 +68,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 */
@@ -102,8 +104,6 @@  extern printf_va_arg_function ** __libc_reg_type_freemem_ptr
 /* 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;
 extern char * __libc_getaliasbyname_freemem_ptr attribute_hidden;
 extern char * __libc_gethostbyaddr_freemem_ptr attribute_hidden;
diff --git a/malloc/set-freeres.c b/malloc/set-freeres.c
index 39fd7d4d27..8112a2f664 100644
--- a/malloc/set-freeres.c
+++ b/malloc/set-freeres.c
@@ -74,8 +74,6 @@ 
 # pragma weak __libc_reg_type_freemem_ptr
 # pragma weak __libc_getgrgid_freemem_ptr
 # pragma weak __libc_getgrnam_freemem_ptr
-# pragma weak __libc_getpwnam_freemem_ptr
-# pragma weak __libc_getpwuid_freemem_ptr
 # pragma weak __libc_getspnam_freemem_ptr
 # pragma weak __libc_getaliasbyname_freemem_ptr
 # pragma weak __libc_gethostbyaddr_freemem_ptr
@@ -124,6 +122,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 ();
 
@@ -188,10 +187,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);
       call_free_static_weak (__libc_getaliasbyname_freemem_ptr);
       call_free_static_weak (__libc_gethostbyaddr_freemem_ptr);
diff --git a/nscd/nscd_proto.h b/nscd/nscd_proto.h
index 6c4a318bc0..da3156b0ff 100644
--- a/nscd/nscd_proto.h
+++ b/nscd/nscd_proto.h
@@ -42,9 +42,11 @@  void __nscd_disable_database (unsigned int db) attribute_hidden;
 extern int __nscd_getpwnam_r (const char *name, struct passwd *resultbuf,
 			      char *buffer, size_t buflen,
 			      struct passwd **result) attribute_hidden;
+int __nscd_getpwnam (const char *name, struct passwd **) attribute_hidden;
 extern int __nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf,
 			      char *buffer,  size_t buflen,
 			      struct passwd **result) attribute_hidden;
+int __nscd_getpwuid (uid_t uid, struct passwd **result) attribute_hidden;
 extern int __nscd_getgrnam_r (const char *name, struct group *resultbuf,
 			      char *buffer, size_t buflen,
 			      struct group **result) attribute_hidden;
diff --git a/nss/Makefile b/nss/Makefile
index 82f5a5d68f..a13f6fa10c 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -47,7 +47,14 @@  routines = \
   nss_files_functions \
   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_hash \
   nss_module \
   nss_parse_line_result \
diff --git a/nss/getpwnam.c b/nss/getpwnam.c
index 36d5a43fd6..55578f7fc0 100644
--- a/nss/getpwnam.c
+++ b/nss/getpwnam.c
@@ -17,12 +17,10 @@ 
 
 #include <pwd.h>
 
+#include <nss_generic.h>
 
-#define LOOKUP_TYPE	struct passwd
-#define FUNCTION_NAME	getpwnam
-#define DATABASE_NAME	passwd
-#define ADD_PARAMS	const char *name
-#define ADD_VARIABLES	name
-#define BUFLEN		NSS_BUFLEN_PASSWD
-
-#include "../nss/getXXbyYY.c"
+struct passwd *
+getpwnam (const char *name)
+{
+  return __nss_getX (nss_lookup_getpwnam, name);
+}
diff --git a/nss/getpwnam_r.c b/nss/getpwnam_r.c
index 1ba6ba53c9..2bb0bfa5e1 100644
--- a/nss/getpwnam_r.c
+++ b/nss/getpwnam_r.c
@@ -17,12 +17,31 @@ 
 
 #include <pwd.h>
 
+#include <nss_generic.h>
+#include <shlib-compat.h>
 
-#define LOOKUP_TYPE	struct passwd
-#define FUNCTION_NAME	getpwnam
-#define DATABASE_NAME	passwd
-#define ADD_PARAMS	const char *name
-#define ADD_VARIABLES	name
-#define BUFLEN		NSS_BUFLEN_PASSWD
+int
+___getpwnam_r (const char *name, struct passwd *pwd,
+	      char *buffer, size_t length, struct passwd **result)
+{
+  void *ptr = pwd;
+  int ret = __nss_getX_r (nss_lookup_getpwnam, name, &ptr, buffer, length);
+  *result = ptr;
+  return ret;
+}
+strong_alias (___getpwnam_r, __getpwnam_r)
+versioned_symbol (libc, ___getpwnam_r, getpwnam_r, GLIBC_2_1_2);
 
-#include <nss/getXXbyYY_r.c>
+#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1_2)
+int
+attribute_compat_text_section
+__old_getpwnam_r (const char *name, struct passwd *pwd,
+		   char *buffer, size_t length, struct passwd **result)
+{
+  int ret = __getpwnam_r (name, pwd, buffer, length, result);
+  if (ret != 0 || result == NULL)
+    ret = -1;
+  return ret;
+}
+compat_symbol (libc, __old_getpwnam_r, getpwnam_r, GLIBC_2_0);
+#endif
diff --git a/nss/getpwuid.c b/nss/getpwuid.c
index 9002dbbc34..96cccdcd89 100644
--- a/nss/getpwuid.c
+++ b/nss/getpwuid.c
@@ -17,12 +17,10 @@ 
 
 #include <pwd.h>
 
+#include <nss_generic.h>
 
-#define LOOKUP_TYPE	struct passwd
-#define FUNCTION_NAME	getpwuid
-#define DATABASE_NAME	passwd
-#define ADD_PARAMS	uid_t uid
-#define ADD_VARIABLES	uid
-#define BUFLEN		NSS_BUFLEN_PASSWD
-
-#include "../nss/getXXbyYY.c"
+struct passwd *
+getpwuid (uid_t uid)
+{
+  return __nss_getX (nss_lookup_getpwuid, &uid);
+}
diff --git a/nss/getpwuid_r.c b/nss/getpwuid_r.c
index 7c6f3970fa..d99846bb57 100644
--- a/nss/getpwuid_r.c
+++ b/nss/getpwuid_r.c
@@ -17,12 +17,31 @@ 
 
 #include <pwd.h>
 
+#include <nss_generic.h>
+#include <shlib-compat.h>
 
-#define LOOKUP_TYPE	struct passwd
-#define FUNCTION_NAME	getpwuid
-#define DATABASE_NAME	passwd
-#define ADD_PARAMS	uid_t uid
-#define ADD_VARIABLES	uid
-#define BUFLEN		NSS_BUFLEN_PASSWD
+int
+___getpwuid_r (uid_t uid, struct passwd *pwd,
+              char *buffer, size_t length, struct passwd **result)
+{
+  void *ptr = pwd;
+  int ret = __nss_getX_r (nss_lookup_getpwuid, &uid, &ptr, buffer, length);
+  *result = ptr;
+  return ret;
+}
+strong_alias (___getpwuid_r, __getpwuid_r)
+versioned_symbol (libc, ___getpwuid_r, getpwuid_r, GLIBC_2_1_2);
 
-#include <nss/getXXbyYY_r.c>
+#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1_2)
+int
+attribute_compat_text_section
+__old_getpwuid_r (uid_t uid, struct passwd *pwd,
+                   char *buffer, size_t length, struct passwd **result)
+{
+  int ret = __getpwuid_r (uid, pwd, buffer, length, result);
+  if (ret != 0 || result == NULL)
+    ret = -1;
+  return ret;
+}
+compat_symbol (libc, __old_getpwuid_r, getpwuid_r, GLIBC_2_0);
+#endif
diff --git a/nss/nss_generic.h b/nss/nss_generic.h
index 8a5b2cb896..107ebb2003 100644
--- a/nss/nss_generic.h
+++ b/nss/nss_generic.h
@@ -80,4 +80,67 @@  typedef const void *nss_lookup_key;
 bool __nscd_generic_get (enum nss_lookup_type lt, nss_lookup_key key,
                          void **result);
 
+/* Invokes the NSS module function SERVICE_FUNCTION of type LT using
+   KEY.  The caller must initialize *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, potentially 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_r, 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;
+
+/* Returns the pointer to the first service lookup function for lookup
+   type LT, or NULL if there are no 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_H */
diff --git a/nss/nss_generic_get.c b/nss/nss_generic_get.c
new file mode 100644
index 0000000000..3319a2b9c0
--- /dev/null
+++ b/nss/nss_generic_get.c
@@ -0,0 +1,61 @@ 
+/* Implementation of the ERANGE protocol for service module functions.
+   Copyright (C) 2026 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..12fedee10f
--- /dev/null
+++ b/nss/nss_generic_get_r.c
@@ -0,0 +1,45 @@ 
+/* Lookup-independent call to an NSS service function.
+   Copyright (C) 2026 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);
+      case nss_lookup_getpwnam:
+        return ((nss_getpwnam_r *) service_function)
+          (key, *result, buffer, length, &errno);
+      case nss_lookup_getpwuid:
+        return ((nss_getpwuid_r *) service_function)
+          (*(const uid_t *) 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..f9e41311bc
--- /dev/null
+++ b/nss/nss_generic_lookup.c
@@ -0,0 +1,51 @@ 
+/* Type-generic lookup of the first NSS service module function.
+   Copyright (C) 2026 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, nscddb)   \
+      case nss_lookup_##name:                           \
+        database = nss_database_##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..abc5a61e11
--- /dev/null
+++ b/nss/nss_generic_next.c
@@ -0,0 +1,60 @@ 
+/* Type-generic next NSS service module function lookup.
+   Copyright (C) 2026 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, nscddb)   \
+      case nss_lookup_##name:                           \
+        database = nss_database_##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_storage.h b/nss/nss_generic_storage.h
new file mode 100644
index 0000000000..334f51be70
--- /dev/null
+++ b/nss/nss_generic_storage.h
@@ -0,0 +1,30 @@ 
+/* Type that can hold all NSS structs.
+   Copyright (C) 2026 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>
+#include <pwd.h>
+
+union nss_generic_storage
+{
+  struct passwd pwd;
+};
+
+#endif /* NSS_GENERIC_STORAGE */
diff --git a/nss/nss_getX.c b/nss/nss_getX.c
new file mode 100644
index 0000000000..8595e5e3b0
--- /dev/null
+++ b/nss/nss_getX.c
@@ -0,0 +1,53 @@ 
+/* Type-generic implementation of public non-_r NSS legacy functions.
+   Copyright (C) 2026 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..b0d488c889
--- /dev/null
+++ b/nss/nss_getX_r.c
@@ -0,0 +1,45 @@ 
+/* Type-generic implementation of public NSS get*_r functions.
+   Copyright (C) 2026 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..0b2588c9fd
--- /dev/null
+++ b/nss/nss_getXinfo.c
@@ -0,0 +1,67 @@ 
+/* Generic malloc-compatible version of NSS get functions.
+   Copyright (C) 2026 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)
+{
+#ifdef USE_NSCD
+  if (__nscd_generic_get (lt, key, result))
+    return 0;
+#endif
+
+  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;
+        }
+
+      /* MERGE is invalid for most databases.  */
+      if ((nss_next_action (nip, status) == NSS_ACTION_MERGE
+           && status == NSS_STATUS_SUCCESS))
+        {
+          __set_errno (EINVAL);
+          status = NSS_STATUS_UNAVAIL;
+          break;
+        }
+
+      no_more = __nss_generic_next (lt, &nip, &fct, status, 0);
+    }
+
+  if (status != NSS_STATUS_SUCCESS)
+    {
+      free (*result);
+      *result = NULL;
+    }
+
+  if (status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND)
+    return 0;
+  else
+    return errno;
+}