[v2,11/23] nscd: Add <concurrent_buffer.h> for nscd client usage

Message ID a216bb6a62ae3bffec9ef07887c950b324cb462a.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-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed

Commit Message

Florian Weimer March 20, 2026, 8:42 p.m. UTC
  This is very specific for parsing the shared nscd mapping.  There
is only enough functionality to follow the hash chain and create
a consistent snapshot of the data stored at a key.  Parsing the
data itself should be handled through <parse_buffer.h>.
---
 nscd/concurrent_buffer.h | 197 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 197 insertions(+)
 create mode 100644 nscd/concurrent_buffer.h
  

Comments

Carlos O'Donell March 24, 2026, 5:56 p.m. UTC | #1
On 3/20/26 4:42 PM, Florian Weimer wrote:
> This is very specific for parsing the shared nscd mapping.  There
> is only enough functionality to follow the hash chain and create
> a consistent snapshot of the data stored at a key.  Parsing the
> data itself should be handled through <parse_buffer.h>.

LGTM.

Switched to atomics for most operations.

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

> ---
>   nscd/concurrent_buffer.h | 197 +++++++++++++++++++++++++++++++++++++++
>   1 file changed, 197 insertions(+)
>   create mode 100644 nscd/concurrent_buffer.h
> 
> diff --git a/nscd/concurrent_buffer.h b/nscd/concurrent_buffer.h
> new file mode 100644
> index 0000000000..925b9f83d1
> --- /dev/null
> +++ b/nscd/concurrent_buffer.h
> @@ -0,0 +1,197 @@
> +/* Parsing of concurrently modified nscd cache structures.
> +   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 CONCURRENT_BUFFER_H
> +#define CONCURRENT_BUFFER_H
> +
> +/* Helper functions for parsing buffers that are concurrently modified.
> +
> +   The helpers perform buffer bounds checking on all accesses.  If an
> +   out-of-bounds access is detected, zero or NULL is returned, and the
> +   buffer is marked as failed.  Unaligned access attempts are treated
> +   as failure.
> +
> +   Also see <parse_buffer.h> for a different kind of parser buffer
> +   that does not support concurrent access.  */
> +
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <string.h>
> +
> +struct concurrent_buffer
> +{
> +  const void *start;
> +  size_t length;
> +};
> +
> +/* Create a concurrent parse buffer for LENGTH bytes at START.  If
> +   LENGTH is zero, the new buffer is immediately marked as failed.  */
> +static inline struct concurrent_buffer
> +cb_create (const void *start, size_t length)
> +{
> +  return (struct concurrent_buffer) { start, length };
> +}
> +
> +/* Mark *CB as failed.  After that, cb_has_failed will return true.  */
> +static inline void
> +cb_mark_failed (struct concurrent_buffer *cb)
> +{
> +  cb->length = 0;
> +}
> +
> +/* Return true if *CB has been marked as failed.  */
> +static inline bool
> +cb_has_failed (const struct concurrent_buffer *cb)
> +{
> +  return cb->length == 0;
> +}
> +
> +/* Check that NEEDED bytes are available in *CB at OFFSET.  Return
> +   false on failure and fail *CB.  A zero value for NEEDED is
> +   considered failure.  */
> +static inline bool
> +cb_available (struct concurrent_buffer *cb, size_t offset, size_t needed)
> +{
> +  size_t last_byte;
> +  if (needed == 0
> +      || __builtin_add_overflow (offset, needed - 1, &last_byte)
> +      || last_byte >= cb->length)
> +    {
> +      cb_mark_failed (cb);
> +      return false;
> +    }
> +  return true;
> +}
> +
> +/* Check that OFFSET leaves room for SIZE bytes in *CB, and that
> +   OFFSET is a multiple of SIZE.  */
> +static inline bool
> +__cb_check_size_alignment (struct concurrent_buffer *cb,
> +                           size_t offset, size_t size)
> +{
> +  if (cb_available (cb, offset, size)
> +      && (offset % size) == 0)
> +    return true;
> +  else
> +    {
> +      cb_mark_failed (cb);
> +      return false;
> +    }
> +}
> +
> +/* Extract an unsigned 8-bit value at OFFSET.  If *CB contains only
> +   OFFSET or fewer bytes, fail *CB and return 0.  */
> +static inline uint8_t
> +cb_u8 (struct concurrent_buffer *cb, size_t offset)
> +{
> +  if (offset >= cb->length)
> +    {
> +      cb_mark_failed (cb);
> +      return 0;
> +    }
> +  /* Relaxed MO is sufficient.  We cannot use atomics because one-byte
> +     inline atomics are not supported on all glibc targets.  */
> +  return *((volatile uint8_t *) cb->start + offset);
> +}
> +
> +/* Extract an unsigned 32-bit value at OFFSET.  If 4 bytes are not
> +   available, fail *CB and return 0.  */
> +static inline uint32_t
> +cb_u32 (struct concurrent_buffer *cb, size_t offset)
> +{
> +  if (__cb_check_size_alignment (cb, offset, 4))
> +    return atomic_load_relaxed ((uint32_t *) ((char *) cb->start + offset));
> +  else
> +    return 0;
> +}
> +
> +/* Extract an unsigned 64-bit value at OFFSET.  If 8 bytes are not
> +   available, fail *CB and return 0.  Always fail if 64-bit atomics
> +   are unavailable.  */
> +static inline uint64_t
> +cb_u64 (struct concurrent_buffer *cb, size_t offset)
> +{
> +#if HAVE_64B_ATOMICS
> +  if (__cb_check_size_alignment (cb, offset, 8))
> +    return atomic_load_relaxed ((uint64_t *) ((char *) cb->start + offset));
> +  else
> +    return 0;
> +#else
> +  cb_mark_failed (cb);
> +  return 0;
> +#endif
> +}
> +
> +/* Extract a signed 32-bit value at OFFSET.  If 4 bytes are not
> +   available, fail *CB and return 0.  */
> +static inline int32_t
> +cb_s32 (struct concurrent_buffer *cb, size_t offset)
> +{
> +  /* Rely on GCC extension for converting to signed.  */
> +  return cb_u32 (cb, offset);
> +}
> +
> +/* Extract a signed 64-bit value at OFFSET.  If 8 bytes are not
> +   available, fail *CB and return 0.  Always fail if 64-bit atomics
> +   are unavailable.  */
> +static inline int64_t
> +cb_s64 (struct concurrent_buffer *cb, size_t offset)
> +{
> +  /* Rely on GCC extension for converting to signed.  */
> +  return cb_u64 (cb, offset);
> +}
> +
> +/* Return A + B.  On overflow, return 0 and mark *CB as failed.  */
> +static inline size_t
> +cb_add (struct concurrent_buffer *cb, size_t a, size_t b)
> +{
> +  size_t result;
> +  if (__builtin_add_overflow (a, b, &result))
> +    {
> +      cb_mark_failed (cb);
> +      return 0;
> +    }
> +  return result;
> +}
> +
> +/* Extract field MEMBER of the struct type STYP from *CB and return
> +   its value.  The struct starts at OFFSET.  Fail *CB and return zero
> +   if the struct field is not available in *CB (the full struct does
> +   not need to be available).  */
> +#define cb_field(cb, offset, styp, member)                     \
> +  (_Generic ((styp) { }.member,                                \
> +             uint8_t: cb_u8,                                   \
> +             uint32_t: cb_u32,                                 \
> +             int32_t: cb_s32,                                  \
> +             int64_t: cb_s64)                                  \
> +   (cb, cb_add (cb, offset, offsetof (styp, member))))
> +
> +/* Return true if the LENGTH bytes at OFFSET match TO_COMPARE, false
> +   otherwise.  If LENGTH bytes are not available, mark *CB as failed.
> +   If the comparison fails, *CB is not marked as failed.  */
> +static inline bool
> +cb_memeq (struct concurrent_buffer *cb, size_t offset,
> +          const void *to_compare, size_t length)
> +{
> +  if (! cb_available (cb, offset, length))
> +    return false;
> +  return memcmp ((char *) cb->start + offset, to_compare, length) == 0;
> +}
> +
> +#endif /* CONCURRENT_BUFFER_H */
  

Patch

diff --git a/nscd/concurrent_buffer.h b/nscd/concurrent_buffer.h
new file mode 100644
index 0000000000..925b9f83d1
--- /dev/null
+++ b/nscd/concurrent_buffer.h
@@ -0,0 +1,197 @@ 
+/* Parsing of concurrently modified nscd cache structures.
+   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 CONCURRENT_BUFFER_H
+#define CONCURRENT_BUFFER_H
+
+/* Helper functions for parsing buffers that are concurrently modified.
+
+   The helpers perform buffer bounds checking on all accesses.  If an
+   out-of-bounds access is detected, zero or NULL is returned, and the
+   buffer is marked as failed.  Unaligned access attempts are treated
+   as failure.
+
+   Also see <parse_buffer.h> for a different kind of parser buffer
+   that does not support concurrent access.  */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+struct concurrent_buffer
+{
+  const void *start;
+  size_t length;
+};
+
+/* Create a concurrent parse buffer for LENGTH bytes at START.  If
+   LENGTH is zero, the new buffer is immediately marked as failed.  */
+static inline struct concurrent_buffer
+cb_create (const void *start, size_t length)
+{
+  return (struct concurrent_buffer) { start, length };
+}
+
+/* Mark *CB as failed.  After that, cb_has_failed will return true.  */
+static inline void
+cb_mark_failed (struct concurrent_buffer *cb)
+{
+  cb->length = 0;
+}
+
+/* Return true if *CB has been marked as failed.  */
+static inline bool
+cb_has_failed (const struct concurrent_buffer *cb)
+{
+  return cb->length == 0;
+}
+
+/* Check that NEEDED bytes are available in *CB at OFFSET.  Return
+   false on failure and fail *CB.  A zero value for NEEDED is
+   considered failure.  */
+static inline bool
+cb_available (struct concurrent_buffer *cb, size_t offset, size_t needed)
+{
+  size_t last_byte;
+  if (needed == 0
+      || __builtin_add_overflow (offset, needed - 1, &last_byte)
+      || last_byte >= cb->length)
+    {
+      cb_mark_failed (cb);
+      return false;
+    }
+  return true;
+}
+
+/* Check that OFFSET leaves room for SIZE bytes in *CB, and that
+   OFFSET is a multiple of SIZE.  */
+static inline bool
+__cb_check_size_alignment (struct concurrent_buffer *cb,
+                           size_t offset, size_t size)
+{
+  if (cb_available (cb, offset, size)
+      && (offset % size) == 0)
+    return true;
+  else
+    {
+      cb_mark_failed (cb);
+      return false;
+    }
+}
+
+/* Extract an unsigned 8-bit value at OFFSET.  If *CB contains only
+   OFFSET or fewer bytes, fail *CB and return 0.  */
+static inline uint8_t
+cb_u8 (struct concurrent_buffer *cb, size_t offset)
+{
+  if (offset >= cb->length)
+    {
+      cb_mark_failed (cb);
+      return 0;
+    }
+  /* Relaxed MO is sufficient.  We cannot use atomics because one-byte
+     inline atomics are not supported on all glibc targets.  */
+  return *((volatile uint8_t *) cb->start + offset);
+}
+
+/* Extract an unsigned 32-bit value at OFFSET.  If 4 bytes are not
+   available, fail *CB and return 0.  */
+static inline uint32_t
+cb_u32 (struct concurrent_buffer *cb, size_t offset)
+{
+  if (__cb_check_size_alignment (cb, offset, 4))
+    return atomic_load_relaxed ((uint32_t *) ((char *) cb->start + offset));
+  else
+    return 0;
+}
+
+/* Extract an unsigned 64-bit value at OFFSET.  If 8 bytes are not
+   available, fail *CB and return 0.  Always fail if 64-bit atomics
+   are unavailable.  */
+static inline uint64_t
+cb_u64 (struct concurrent_buffer *cb, size_t offset)
+{
+#if HAVE_64B_ATOMICS
+  if (__cb_check_size_alignment (cb, offset, 8))
+    return atomic_load_relaxed ((uint64_t *) ((char *) cb->start + offset));
+  else
+    return 0;
+#else
+  cb_mark_failed (cb);
+  return 0;
+#endif
+}
+
+/* Extract a signed 32-bit value at OFFSET.  If 4 bytes are not
+   available, fail *CB and return 0.  */
+static inline int32_t
+cb_s32 (struct concurrent_buffer *cb, size_t offset)
+{
+  /* Rely on GCC extension for converting to signed.  */
+  return cb_u32 (cb, offset);
+}
+
+/* Extract a signed 64-bit value at OFFSET.  If 8 bytes are not
+   available, fail *CB and return 0.  Always fail if 64-bit atomics
+   are unavailable.  */
+static inline int64_t
+cb_s64 (struct concurrent_buffer *cb, size_t offset)
+{
+  /* Rely on GCC extension for converting to signed.  */
+  return cb_u64 (cb, offset);
+}
+
+/* Return A + B.  On overflow, return 0 and mark *CB as failed.  */
+static inline size_t
+cb_add (struct concurrent_buffer *cb, size_t a, size_t b)
+{
+  size_t result;
+  if (__builtin_add_overflow (a, b, &result))
+    {
+      cb_mark_failed (cb);
+      return 0;
+    }
+  return result;
+}
+
+/* Extract field MEMBER of the struct type STYP from *CB and return
+   its value.  The struct starts at OFFSET.  Fail *CB and return zero
+   if the struct field is not available in *CB (the full struct does
+   not need to be available).  */
+#define cb_field(cb, offset, styp, member)                     \
+  (_Generic ((styp) { }.member,                                \
+             uint8_t: cb_u8,                                   \
+             uint32_t: cb_u32,                                 \
+             int32_t: cb_s32,                                  \
+             int64_t: cb_s64)                                  \
+   (cb, cb_add (cb, offset, offsetof (styp, member))))
+
+/* Return true if the LENGTH bytes at OFFSET match TO_COMPARE, false
+   otherwise.  If LENGTH bytes are not available, mark *CB as failed.
+   If the comparison fails, *CB is not marked as failed.  */
+static inline bool
+cb_memeq (struct concurrent_buffer *cb, size_t offset,
+          const void *to_compare, size_t length)
+{
+  if (! cb_available (cb, offset, length))
+    return false;
+  return memcmp ((char *) cb->start + offset, to_compare, length) == 0;
+}
+
+#endif /* CONCURRENT_BUFFER_H */