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

Florian Weimer fweimer@redhat.com
Fri Mar 20 20:42:40 GMT 2026


Implement passwd and group handling only at this point.
---
 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 */
-- 
2.53.0




More information about the Libc-alpha mailing list