[PATCH v2 15/23] nscd: Add __nscd_generic_get for generic lookup

Carlos O'Donell carlos@redhat.com
Tue Mar 24 21:11:35 GMT 2026


On 3/20/26 4:42 PM, Florian Weimer wrote:
> Implement passwd and group handling only at this point.

LGTM.

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

> ---
>   nscd/nscd_helper.c | 318 +++++++++++++++++++++++++++++++++++++++++++++
>   nss/nss_generic.h  |  16 +++
>   2 files changed, 334 insertions(+)
> 
> diff --git a/nscd/nscd_helper.c b/nscd/nscd_helper.c
> index 88bfb463c2..b113403f16 100644
> --- a/nscd/nscd_helper.c
> +++ b/nscd/nscd_helper.c
> @@ -38,6 +38,9 @@
>   #include <nss.h>
>   #include <struct___timespec64.h>
>   #include <scratch_buffer.h>
> +#include <parse_buffer.h>
> +#include <nss/nss_generic.h>
> +#include <grp.h>
>   
>   #include "nscd-client.h"
>   #include "nscd-dbtype.h"
> @@ -802,3 +805,318 @@ __nscd_read_from_socket (const char *key, size_t keylen, request_type type,
>   
>     return ret;
>   }
> +
> +/* Used to implement __nscd_parse_and_copy.  */
> +static int __nscd_parse_and_copy_group (enum nss_lookup_type,
> +					void *, ssize_t, void **);
> +static int __nscd_parse_and_copy_passwd (enum nss_lookup_type,
> +					 void *, ssize_t, void **);
> +
> +/* Use __nss_generic_dup to copy NSS data according to LT at BUFFER
> +   (of LENGTH bytes) to *RESULT, or NULL if there is no data.  Return
> +   0 for success (including a negative result), or a negative error
> +   code: -ENOENT for no data available,, -EMSGSIZE for a parse
> +   failure, and -ENOMEM for a memory allocation error.  */
> +static int
> +__nscd_parse_and_copy (enum nss_lookup_type lt,
> +		       void *buffer, ssize_t length, void **result)
> +{
> +  switch (lt)
> +    {
> +    case nss_lookup_getgrnam:
> +    case nss_lookup_getgrgid:
> +      return __nscd_parse_and_copy_group (lt, buffer, length, result);
> +    case nss_lookup_getpwuid:
> +    case nss_lookup_getpwnam:
> +      return __nscd_parse_and_copy_passwd (lt, buffer, length, result);
> +    }
> +  __builtin_unreachable ();
> +}
> +
> +/* Use the nscd shared memory cache to retrieve data.  On success,
> +   return true and write a __nss_generic_dup-allocated NSS struct
> +   pointer to *RESULT (positive result), or write NULL to *RESULT
> +   (negative result).  On failurem return false.  Failure can mean:
> +   the data is not in the mapping, the mapping is being concurrently
> +   modified, or there was a resource shortage.  *BUFFER can be used
> +   for temporary storage (but not for data in the returned result).  */
> +static bool
> +__nscd_try_cache (unsigned int db, request_type type,
> +		  const char *key, size_t keylen, enum nss_lookup_type lt,
> +		  struct scratch_buffer *buffer, void **result)
> +{
> +  assert (db < lastdb);
> +  struct mapped_database *mapped = &__nscd_mapped_databases[db];
> +  bool found = false;
> +
> +  for (int retries = 0; ; ++retries)
> +    {
> +      __libc_rwlock_rdlock (mapped->lock);
> +
> +      /* Check if the mapping is usable.  If not, try to replace it.  */
> +      if (mapped->mapsize == 0
> +	  || (mapped->head->nscd_certainly_running == 0
> +	      && mapped->head->timestamp + MAPPING_TIMEOUT < time_now ())
> +	  || mapped->head->data_size > mapped->datasize)
> +	{
> +	  /* Mapping is not usable as-is.  Attempt to replace it.  */
> +	  __libc_rwlock_unlock (mapped->lock);
> +	  __nscd_get_mapping (db);
> +	  if (mapped->mapsize == 0)
> +	    /* Could not get mapping.  */
> +	    break;
> +	}
> +
> +      ssize_t ret = __nscd_read_from_cache (key, keylen, type, buffer,
> +					    mapped);
> +      if (ret > 0)
> +	{
> +	  ret = __nscd_parse_and_copy (lt, buffer->data, ret, result);
> +	  if (ret == 0)
> +	    {
> +	      /* We got the data (result can be NULL for negative).  */
> +	      found = true;
> +	      break;
> +	    }
> +	  else if (ret != -EMSGSIZE)
> +	    /* Some other error.  Give up.  */
> +	    break;
> +	  /* Else fall through and retry.  */
> +	}
> +      else if (ret != -EINPROGRESS && ret != -EMSGSIZE)
> +	/* An error that does not go away with retrying.  */
> +	break;
> +
> +      if (retries == 5)
> +	/* Stop retrying.   */
> +	break;
> +
> +      __libc_rwlock_unlock (mapped->lock);
> +    }
> +
> +  __libc_rwlock_unlock (mapped->lock);
> +  return found;
> +}
> +
> +/* Use the nscd shared memory cache to retrieve data.  On success,
> +   write a pointer to an NSS struct to *RESULT, allocated using
> +   __nss_generic_dup according to LT, or write NULL to *RESULT
> +   (negative result) and return true.  On failure, return false.
> +   Failure can mean failure to connect to nscd, or a parse error in
> +   the data returned from nscd.  */
> +static bool
> +__nscd_try_socket (request_type type, const char *key, size_t keylen,
> +		   enum nss_lookup_type lt, struct scratch_buffer *buffer,
> +		   void **result)
> +{
> +  ssize_t length = __nscd_read_from_socket (key, keylen, type, buffer);
> +  /* The length can be negative.  Rely on the check in the parse function.  */
> +  int ret = __nscd_parse_and_copy (lt, buffer->data, length, result);
> +  return ret == 0;
> +}
> +
> +/* Common tail of __nscd_parse_and_copy_* implementations.  Return 0
> +   on success, -ENOMEM on failure, and -EMSGSIZE on parse error.  */
> +static inline int
> +__nscd_parse_and_copy_tail (enum nss_lookup_type lt, struct parse_buffer pb,
> +			    const void *nss_struct, void **result)
> +{
> +  if (!parse_buffer_has_failed (&pb))
> +    {
> +      *result = __nss_generic_dup (lt, nss_struct);
> +      if (*result != NULL)
> +	return 0;
> +      else
> +	return -ENOMEM;
> +    }
> +  else
> +    /* Parse error.  */
> +    return -EMSGSIZE;
> +}
> +
> +static int
> +__nscd_parse_and_copy_group (enum nss_lookup_type lt,
> +			     void *buffer, ssize_t length, void **result)
> +{
> +  if (length < 0)
> +    {
> +      __nscd_defer_database (NSS_DBSIDX_group);
> +      return -ENOENT;
> +    }
> +
> +  struct parse_buffer pb = parse_buffer_create (buffer, length);
> +
> +  int32_t gr_found = parse_buffer_field (&pb, 0, gr_response_header, found);
> +  if (gr_found == 1)
> +    {
> +      uint32_t gr_mem_cnt
> +	= parse_buffer_field (&pb, 0, gr_response_header, gr_mem_cnt);
> +
> +      /* Paranoia check to avoid overflow.  */
> +      if (gr_mem_cnt >= INT_MAX / 4)
> +	return -EMSGSIZE;
> +
> +      /* Skip over the array with the group member name lengths.  */
> +      size_t pos = (sizeof (gr_response_header)
> +		    + gr_mem_cnt * sizeof (uint32_t));
> +      struct group grp;
> +      grp.gr_name = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, gr_response_header, gr_name_len));
> +      grp.gr_passwd = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, gr_response_header, gr_passwd_len));
> +      grp.gr_gid = parse_buffer_field (&pb, 0,
> +				       gr_response_header, gr_gid);
> +      grp.gr_mem = __libc_reallocarray (NULL, gr_mem_cnt + 1,
> +					sizeof (*grp.gr_mem));
> +      if (grp.gr_mem == NULL)
> +	return -ENOMEM;
> +
> +      /* Need to free grp.gr_mem before returning after this point.  */
> +      int ret = 0;
> +
> +      size_t lenpos = sizeof (gr_response_header);
> +      for (uint32_t i = 0; i < gr_mem_cnt; ++i)
> +	grp.gr_mem[i] = parse_buffer_sized_cstring_advance
> +	  (&pb, &pos, parse_buffer_u32_advance (&pb, &lenpos));
> +      grp.gr_mem[gr_mem_cnt] = NULL;
> +
> +      ret = __nscd_parse_and_copy_tail (lt, pb, &grp, result);
> +      free (grp.gr_mem);
> +      return ret;
> +    }
> +  else if (gr_found == -1)
> +    {
> +      /* The daemon does not cache this database.  */
> +      __nscd_defer_database (NSS_DBSIDX_group);
> +      return -ENOENT;
> +    }
> +  else if (parse_buffer_has_failed (&pb))
> +    return -EMSGSIZE;
> +  else
> +    {
> +      *result = NULL;
> +      return 0;
> +    }
> +}
> +
> +
> +/* Common tail of __nscd_generic_get.  */
> +static bool
> +__nscd_generic_get_tail (enum nss_lookup_type lt, unsigned int db,
> +			 request_type type,
> +			 const char *nscd_key, size_t nscd_keylen,
> +			 void **result)
> +{
> +  bool ok = true;
> +  struct scratch_buffer buffer;
> +  scratch_buffer_init (&buffer);
> +
> +  /* Try the mapping first.  */
> +  bool cache_hit = __nscd_try_cache (db, type, nscd_key, nscd_keylen,
> +				     lt, &buffer, result);
> +
> +  /* If the cache did not produce data, try the socket.  The socket
> +     may produce a successful negative result (*result == NULL).  */
> +  if (!cache_hit)
> +    ok = __nscd_try_socket (type, nscd_key, nscd_keylen, lt, &buffer,
> +			    result);
> +
> +  scratch_buffer_free (&buffer);
> +  return ok;
> +}
> +
> +#define NSCD_NUMBER_BUFFER_LENGTH 20
> +
> +static int
> +__nscd_fmt_num (char storage[static NSCD_NUMBER_BUFFER_LENGTH], int number)
> +{
> +  return __snprintf (storage, NSCD_NUMBER_BUFFER_LENGTH, "%d", number) + 1;
> +}
> +
> +bool
> +__nscd_generic_get (enum nss_lookup_type lt, const void *key, void **result)
> +{
> +  /* Storage for keys that need to be converted.  */
> +  char storage[20];
> +
> +  int8_t db = __nscd_database_for_lookup[lt];
> +  if (db < 0 || !__nscd_use_database (db))
> +    return false;
> +
> +  switch (lt)
> +    {
> +      case nss_lookup_getgrgid:
> +	return __nscd_generic_get_tail (lt, db, GETGRBYGID, storage,
> +					__nscd_fmt_num (storage,
> +							*(const gid_t *) key),
> +					result);
> +      case nss_lookup_getgrnam:
> +	return __nscd_generic_get_tail (lt, db, GETGRBYNAME,
> +					key, strlen (key) + 1,
> +					result);
> +      case nss_lookup_getpwnam:
> +	return __nscd_generic_get_tail (lt, db, GETPWBYNAME,
> +					key, strlen (key) + 1,
> +					result);
> +      case nss_lookup_getpwuid:
> +	return __nscd_generic_get_tail (lt, db, GETPWBYUID, storage,
> +					__nscd_fmt_num (storage,
> +							*(const uid_t *) key),
> +					result);
> +    }
> +  __builtin_unreachable ();
> +}
> +
> +static int
> +__nscd_parse_and_copy_passwd (enum nss_lookup_type lt,
> +			      void *buffer, ssize_t length, void **result)
> +{
> +  if (length < 0)
> +    {
> +      __nscd_defer_database (NSS_DBSIDX_passwd);
> +      return -ENOENT;
> +    }
> +
> +  struct parse_buffer pb = parse_buffer_create (buffer, length);
> +
> +  int32_t pw_found = parse_buffer_field (&pb, 0, pw_response_header, found);
> +  if (pw_found == 1)
> +    {
> +      size_t pos = sizeof (pw_response_header);
> +      struct passwd pwd;
> +      pwd.pw_name = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_name_len));
> +      pwd.pw_passwd = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_passwd_len));
> +      pwd.pw_uid = parse_buffer_field (&pb, 0, pw_response_header, pw_uid);
> +      pwd.pw_gid = parse_buffer_field (&pb, 0, pw_response_header, pw_gid);
> +      pwd.pw_gecos = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_gecos_len));
> +      pwd.pw_dir = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_dir_len));
> +      pwd.pw_shell = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_shell_len));
> +
> +      return __nscd_parse_and_copy_tail (lt, pb, &pwd, result);
> +    }
> +  else if (pw_found == -1)
> +    {
> +      /* The daemon does not cache this database.  */
> +      __nscd_defer_database (NSS_DBSIDX_passwd);
> +      return -ENOENT;
> +    }
> +  else if (parse_buffer_has_failed (&pb))
> +    return -EMSGSIZE;
> +  else
> +    {
> +      *result = NULL;
> +      return 0;
> +    }
> +}
> diff --git a/nss/nss_generic.h b/nss/nss_generic.h
> index fcddf8bf54..8a5b2cb896 100644
> --- a/nss/nss_generic.h
> +++ b/nss/nss_generic.h
> @@ -64,4 +64,20 @@ void *__nss_generic_dup (enum nss_lookup_type lt, const void *source)
>      each lookup type, or -1 if the lookup has no corresponding database.  */
>   extern int8_t __nscd_database_for_lookup[nss_lookup_MAX] attribute_hidden;
>   
> +/* Interpreted according to enum nss_lookup_type.  Typically a string
> +   or a pointer to an integer.  */
> +typedef const void *nss_lookup_key;
> +
> +/* Query the nscd daemon for KEY according to LT.  On success, return
> +   true, and write a NSS struct pointer allocated using
> +   __nss_generic_dup to *RESULT (positive result), or a null pointer
> +   (negative result).  On failure, return false.  The caller is
> +   expected to attempt the NSS operation directly after a failure.
> +
> +   Failure is also possible if nscd lookup has been deferred; the
> +   implementation calls __nscd_use_database as a first step to check
> +   if nscd should actually be used.  */
> +bool __nscd_generic_get (enum nss_lookup_type lt, nss_lookup_key key,
> +                         void **result);
> +
>   #endif /* NSS_GENERIC_H */


-- 
Cheers,
Carlos.



More information about the Libc-alpha mailing list