[v2,19/23] nss: Low-level functionality for merging group lists

Message ID 0fe187adee88605e0796966e9ef9b898a34d94f3.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
  Use <stringtable.h> to deduplicate members, with a single-linked
list to preserve ordering.
---
 nss/Makefile                |  2 +
 nss/nss_group_members.c     | 80 +++++++++++++++++++++++++++++++++++++
 nss/nss_group_members.h     | 61 ++++++++++++++++++++++++++++
 nss/tst-nss_group_members.c | 77 +++++++++++++++++++++++++++++++++++
 4 files changed, 220 insertions(+)
 create mode 100644 nss/nss_group_members.c
 create mode 100644 nss/nss_group_members.h
 create mode 100644 nss/tst-nss_group_members.c
  

Comments

Carlos O'Donell March 24, 2026, 9:38 p.m. UTC | #1
On 3/20/26 4:43 PM, Florian Weimer wrote:
> Use <stringtable.h> to deduplicate members, with a single-linked
> list to preserve ordering.

LGTM.

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

> ---
>   nss/Makefile                |  2 +
>   nss/nss_group_members.c     | 80 +++++++++++++++++++++++++++++++++++++
>   nss/nss_group_members.h     | 61 ++++++++++++++++++++++++++++
>   nss/tst-nss_group_members.c | 77 +++++++++++++++++++++++++++++++++++
>   4 files changed, 220 insertions(+)
>   create mode 100644 nss/nss_group_members.c
>   create mode 100644 nss/nss_group_members.h
>   create mode 100644 nss/tst-nss_group_members.c
> 
> diff --git a/nss/Makefile b/nss/Makefile
> index ec68954f3b..82f5a5d68f 100644
> --- a/nss/Makefile
> +++ b/nss/Makefile
> @@ -89,6 +89,7 @@ routines += \
>     getgrnam_r \
>     grp-merge \
>     initgroups \
> +  nss_group_members \
>     putgrent \
>     # routines
>   
> @@ -315,6 +316,7 @@ tests-internal := \
>     tst-field \
>     tst-nss_generic_copy \
>     tst-nss_generic_dup \
> +  tst-nss_group_members \

OK. tests-internal.

>     tst-rfc3484 \
>     tst-rfc3484-2 \
>     tst-rfc3484-3 \
> diff --git a/nss/nss_group_members.c b/nss/nss_group_members.c
> new file mode 100644
> index 0000000000..aafbc8e65b
> --- /dev/null
> +++ b/nss/nss_group_members.c
> @@ -0,0 +1,80 @@
> +/* Support for merging group member lists.  Implementation.
> +   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_group_members.h>
> +
> +#include <assert.h>
> +#include <grp.h>
> +#include <stdlib.h>
> +
> +void
> +__nss_group_members_init (struct nss_group_members *table)
> +{
> +  *table = (struct nss_group_members)
> +    {
> +      .tail = &table->head,
> +    };
> +}
> +
> +void
> +__nss_group_members_free (struct nss_group_members *table)
> +{
> +  __nss_group_members__free (table);
> +  __nss_group_members_init (table);
> +}
> +
> +bool
> +__nss_group_members_add (struct nss_group_members *table,
> +                         const struct group *grp)
> +{
> +  if (grp->gr_mem == NULL)
> +    return true;
> +  for (char **p = grp->gr_mem; *p != NULL; ++p)
> +    {
> +      struct nss_group_members_entry *e = __nss_group_members__add (table, *p);
> +      if (e == NULL)
> +        return false;
> +      /* If the element is freshly inserted, e->order is NULL.
> +         However, e->order may be NULL if e is not freshly inserted
> +         and e is the last element in the ordering chain, which has no
> +         successor.  */
> +      if (e->order == NULL && &e->order != table->tail)
> +        {
> +          *table->tail = e;
> +          table->tail = &e->order;
> +        }
> +    }
> +  return true;
> +}
> +
> +char **
> +__nss_group_members (const struct nss_group_members *table)
> +{
> +  char **list = malloc ((table->T.count + 1) * sizeof (*list));
> +  if (list == NULL)
> +    return NULL;
> +
> +  size_t i = 0;
> +  for (struct nss_group_members_entry *p = table->head; p != NULL;
> +       p = p->order, ++i)
> +    list[i] = p->E.string;
> +  assert (i == table->T.count);
> +  list[i] = NULL;
> +
> +  return list;
> +}
> diff --git a/nss/nss_group_members.h b/nss/nss_group_members.h
> new file mode 100644
> index 0000000000..19bfccfc31
> --- /dev/null
> +++ b/nss/nss_group_members.h
> @@ -0,0 +1,61 @@
> +/* Support for merging group member lists.
> +   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_GROUP_MEMBERS_H
> +#define NSS_GROUP_MEMBERS_H
> +
> +#include <stdbool.h>
> +#include <stringtable.h>
> +
> +struct nss_group_members_entry
> +{
> +  struct nss_group_members_entry *order;
> +  struct stringtable_entry E;
> +};
> +
> +struct nss_group_members
> +{
> +  struct stringtable T;
> +  struct nss_group_members_entry *head;
> +  struct nss_group_members_entry **tail;
> +};
> +
> +#define STRINGTABLE_ENTRY nss_group_members_entry
> +#define STRINGTABLE_STRUCT nss_group_members
> +#define STRINGTABLE_PREFIX __nss_group_members__
> +#include <stringtable-skeleton.h>

OK.

> +
> +/* Initialize a group members table.  */
> +void __nss_group_members_init (struct nss_group_members *) attribute_hidden;
> +
> +/* Deallocate a group members table.  The table is initialized afterwards.  */
> +void __nss_group_members_free (struct nss_group_members *) attribute_hidden;
> +
> +/* Add the members from GRP to TABLE.  Returns true on success, false
> +   on memory allocation failure.  */
> +struct group;
> +bool __nss_group_members_add (struct nss_group_members *table,
> +                              const struct group *grp) attribute_hidden;
> +
> +/* Return a NULL-terminated array of strings, in the order they have
> +   been added to the table.  The strings point into the table.  The
> +   returned pointer should be passed to free.  Return NULL on memory
> +   allocation failure.  */
> +char **__nss_group_members (const struct nss_group_members *) attribute_hidden;
> +
> +#endif /* NSS_GROUP_MEMBERS_H */
> diff --git a/nss/tst-nss_group_members.c b/nss/tst-nss_group_members.c
> new file mode 100644
> index 0000000000..43f26ff27b
> --- /dev/null
> +++ b/nss/tst-nss_group_members.c
> @@ -0,0 +1,77 @@
> +/* Test infrastructure for merging group member lists.
> +   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 <grp.h>
> +#include <nss_generic.h>
> +#include <nss_group_members.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +
> +static int
> +do_test (void)
> +{
> +  struct nss_group_members t;
> +  __nss_group_members_init (&t);
> +
> +  for (unsigned int count = 0; count < 100; ++count)
> +    {
> +      struct group grp = { };
> +      grp.gr_mem = xcalloc (count + 1, sizeof (*grp.gr_mem));
> +      for (unsigned int i = 0; i < count; ++i)
> +        grp.gr_mem[i] = xasprintf ("user%d", i);
> +
> +      /* This contains the expected list for the result.  */
> +      struct group *grp_copy = __nss_generic_dup (nss_lookup_getgrgid, &grp);
> +
> +      TEST_VERIFY (__nss_group_members_add (&t, &grp));
> +
> +      for (unsigned int i = 0; i < count; ++i)
> +        free (grp.gr_mem[i]);
> +
> +      /* Add some duplicates in a random-looking order.  */
> +      for (unsigned int i = 0; i < count / 2; ++i)
> +        grp.gr_mem[i] = xasprintf ("user%d", (i * 0x9e3779b9) % count);
> +      grp.gr_mem[count / 2] = NULL;
> +
> +      /* Merge.  */
> +      TEST_VERIFY (__nss_group_members_add (&t, &grp));
> +
> +      for (unsigned int i = 0; i < count / 2; ++i)
> +        free (grp.gr_mem[i]);
> +      free (grp.gr_mem);
> +
> +      /* Compare the results.  */
> +      char **list = __nss_group_members (&t);
> +      for (unsigned int i = 0; i <= count; ++i)
> +        TEST_COMPARE_STRING (list[i], grp_copy->gr_mem[i]);
> +
> +      free (list);
> +      __nss_group_members_free (&t);
> +      free (grp_copy);
> +    }
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> +
> +/* Re-compile the support code because it is not exported from libc.  */
> +#include <nss_generic_copy.c>
> +#include <nss_generic_dup.c>
> +#include <nss_group_members.c>

OK. Interesting to see this work, but it should.
  

Patch

diff --git a/nss/Makefile b/nss/Makefile
index ec68954f3b..82f5a5d68f 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -89,6 +89,7 @@  routines += \
   getgrnam_r \
   grp-merge \
   initgroups \
+  nss_group_members \
   putgrent \
   # routines
 
@@ -315,6 +316,7 @@  tests-internal := \
   tst-field \
   tst-nss_generic_copy \
   tst-nss_generic_dup \
+  tst-nss_group_members \
   tst-rfc3484 \
   tst-rfc3484-2 \
   tst-rfc3484-3 \
diff --git a/nss/nss_group_members.c b/nss/nss_group_members.c
new file mode 100644
index 0000000000..aafbc8e65b
--- /dev/null
+++ b/nss/nss_group_members.c
@@ -0,0 +1,80 @@ 
+/* Support for merging group member lists.  Implementation.
+   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_group_members.h>
+
+#include <assert.h>
+#include <grp.h>
+#include <stdlib.h>
+
+void
+__nss_group_members_init (struct nss_group_members *table)
+{
+  *table = (struct nss_group_members)
+    {
+      .tail = &table->head,
+    };
+}
+
+void
+__nss_group_members_free (struct nss_group_members *table)
+{
+  __nss_group_members__free (table);
+  __nss_group_members_init (table);
+}
+
+bool
+__nss_group_members_add (struct nss_group_members *table,
+                         const struct group *grp)
+{
+  if (grp->gr_mem == NULL)
+    return true;
+  for (char **p = grp->gr_mem; *p != NULL; ++p)
+    {
+      struct nss_group_members_entry *e = __nss_group_members__add (table, *p);
+      if (e == NULL)
+        return false;
+      /* If the element is freshly inserted, e->order is NULL.
+         However, e->order may be NULL if e is not freshly inserted
+         and e is the last element in the ordering chain, which has no
+         successor.  */
+      if (e->order == NULL && &e->order != table->tail)
+        {
+          *table->tail = e;
+          table->tail = &e->order;
+        }
+    }
+  return true;
+}
+
+char **
+__nss_group_members (const struct nss_group_members *table)
+{
+  char **list = malloc ((table->T.count + 1) * sizeof (*list));
+  if (list == NULL)
+    return NULL;
+
+  size_t i = 0;
+  for (struct nss_group_members_entry *p = table->head; p != NULL;
+       p = p->order, ++i)
+    list[i] = p->E.string;
+  assert (i == table->T.count);
+  list[i] = NULL;
+
+  return list;
+}
diff --git a/nss/nss_group_members.h b/nss/nss_group_members.h
new file mode 100644
index 0000000000..19bfccfc31
--- /dev/null
+++ b/nss/nss_group_members.h
@@ -0,0 +1,61 @@ 
+/* Support for merging group member lists.
+   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_GROUP_MEMBERS_H
+#define NSS_GROUP_MEMBERS_H
+
+#include <stdbool.h>
+#include <stringtable.h>
+
+struct nss_group_members_entry
+{
+  struct nss_group_members_entry *order;
+  struct stringtable_entry E;
+};
+
+struct nss_group_members
+{
+  struct stringtable T;
+  struct nss_group_members_entry *head;
+  struct nss_group_members_entry **tail;
+};
+
+#define STRINGTABLE_ENTRY nss_group_members_entry
+#define STRINGTABLE_STRUCT nss_group_members
+#define STRINGTABLE_PREFIX __nss_group_members__
+#include <stringtable-skeleton.h>
+
+/* Initialize a group members table.  */
+void __nss_group_members_init (struct nss_group_members *) attribute_hidden;
+
+/* Deallocate a group members table.  The table is initialized afterwards.  */
+void __nss_group_members_free (struct nss_group_members *) attribute_hidden;
+
+/* Add the members from GRP to TABLE.  Returns true on success, false
+   on memory allocation failure.  */
+struct group;
+bool __nss_group_members_add (struct nss_group_members *table,
+                              const struct group *grp) attribute_hidden;
+
+/* Return a NULL-terminated array of strings, in the order they have
+   been added to the table.  The strings point into the table.  The
+   returned pointer should be passed to free.  Return NULL on memory
+   allocation failure.  */
+char **__nss_group_members (const struct nss_group_members *) attribute_hidden;
+
+#endif /* NSS_GROUP_MEMBERS_H */
diff --git a/nss/tst-nss_group_members.c b/nss/tst-nss_group_members.c
new file mode 100644
index 0000000000..43f26ff27b
--- /dev/null
+++ b/nss/tst-nss_group_members.c
@@ -0,0 +1,77 @@ 
+/* Test infrastructure for merging group member lists.
+   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 <grp.h>
+#include <nss_generic.h>
+#include <nss_group_members.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+
+static int
+do_test (void)
+{
+  struct nss_group_members t;
+  __nss_group_members_init (&t);
+
+  for (unsigned int count = 0; count < 100; ++count)
+    {
+      struct group grp = { };
+      grp.gr_mem = xcalloc (count + 1, sizeof (*grp.gr_mem));
+      for (unsigned int i = 0; i < count; ++i)
+        grp.gr_mem[i] = xasprintf ("user%d", i);
+
+      /* This contains the expected list for the result.  */
+      struct group *grp_copy = __nss_generic_dup (nss_lookup_getgrgid, &grp);
+
+      TEST_VERIFY (__nss_group_members_add (&t, &grp));
+
+      for (unsigned int i = 0; i < count; ++i)
+        free (grp.gr_mem[i]);
+
+      /* Add some duplicates in a random-looking order.  */
+      for (unsigned int i = 0; i < count / 2; ++i)
+        grp.gr_mem[i] = xasprintf ("user%d", (i * 0x9e3779b9) % count);
+      grp.gr_mem[count / 2] = NULL;
+
+      /* Merge.  */
+      TEST_VERIFY (__nss_group_members_add (&t, &grp));
+
+      for (unsigned int i = 0; i < count / 2; ++i)
+        free (grp.gr_mem[i]);
+      free (grp.gr_mem);
+
+      /* Compare the results.  */
+      char **list = __nss_group_members (&t);
+      for (unsigned int i = 0; i <= count; ++i)
+        TEST_COMPARE_STRING (list[i], grp_copy->gr_mem[i]);
+
+      free (list);
+      __nss_group_members_free (&t);
+      free (grp_copy);
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
+
+/* Re-compile the support code because it is not exported from libc.  */
+#include <nss_generic_copy.c>
+#include <nss_generic_dup.c>
+#include <nss_group_members.c>