[libc-commits] [libc] [libc] Add setgroups and initgroups entrypoints (PR #227272)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Tue Sep 29 03:57:21 PDT 2026


https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/227272

Add the setgroups and initgroups entrypoints from <grp.h> (GNU extensions).

A Linux syscall wrapper is added for setgroups, supporting both SYS_setgroups and SYS_setgroups32. The initgroups entrypoint initialises the supplementary group access list for a specified user by reading the group database using a scoped database stream and setting the resulting groups via setgroups.

Assisted-by: Automated tooling, human reviewed.

>From b0d9c71984e855a6c007a51fbbe62def1b4f8380 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Thu, 17 Sep 2026 16:18:12 +0100
Subject: [PATCH] [libc] Add setgroups and initgroups entrypoints

Add the setgroups and initgroups entrypoints from <grp.h> (GNU
extensions).

A Linux syscall wrapper is added for setgroups, supporting both
SYS_setgroups and SYS_setgroups32. The initgroups entrypoint
initialises the supplementary group access list for a specified user
by reading the group database using a scoped database stream and
setting the resulting groups via setgroups.

Assisted-by: Automated tooling, human reviewed.
---
 libc/config/linux/aarch64/entrypoints.txt     |   2 +
 libc/config/linux/arm/entrypoints.txt         |   2 +
 libc/config/linux/i386/entrypoints.txt        |   2 +
 libc/config/linux/riscv/entrypoints.txt       |   2 +
 libc/config/linux/x86_64/entrypoints.txt      |   2 +
 libc/include/grp.yaml                         |  14 +++
 .../linux/syscall_wrappers/CMakeLists.txt     |  14 +++
 .../OSUtil/linux/syscall_wrappers/setgroups.h |  43 ++++++++
 libc/src/grp/CMakeLists.txt                   |  34 ++++++
 libc/src/grp/grp_utils.cpp                    |  93 +++++++++-------
 libc/src/grp/grp_utils.h                      |   5 +
 libc/src/grp/initgroups.cpp                   |  35 ++++++
 libc/src/grp/initgroups.h                     |  26 +++++
 libc/src/grp/setgroups.cpp                    |  37 +++++++
 libc/src/grp/setgroups.h                      |  27 +++++
 libc/test/src/grp/CMakeLists.txt              |  45 ++++++++
 libc/test/src/grp/initgroups_test.cpp         | 102 ++++++++++++++++++
 libc/test/src/grp/setgroups_test.cpp          |  48 +++++++++
 18 files changed, 497 insertions(+), 36 deletions(-)
 create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/setgroups.h
 create mode 100644 libc/src/grp/initgroups.cpp
 create mode 100644 libc/src/grp/initgroups.h
 create mode 100644 libc/src/grp/setgroups.cpp
 create mode 100644 libc/src/grp/setgroups.h
 create mode 100644 libc/test/src/grp/initgroups_test.cpp
 create mode 100644 libc/test/src/grp/setgroups_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 140f3dde5329c..9a59ec2b93dee 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -54,7 +54,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.grp.getgrouplist
     libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
+    libc.src.grp.initgroups
     libc.src.grp.setgrent
+    libc.src.grp.setgroups
 
     # poll.h entrypoints
     libc.src.poll.poll
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index ed4267fa499cc..5f6f4ee9d558a 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -38,7 +38,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.grp.getgrouplist
     libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
+    libc.src.grp.initgroups
     libc.src.grp.setgrent
+    libc.src.grp.setgroups
 
     # poll.h entrypoints
     libc.src.poll.poll
diff --git a/libc/config/linux/i386/entrypoints.txt b/libc/config/linux/i386/entrypoints.txt
index 9ad8e7f160064..b0562e4227344 100644
--- a/libc/config/linux/i386/entrypoints.txt
+++ b/libc/config/linux/i386/entrypoints.txt
@@ -10,7 +10,9 @@ set(TARGET_LIBC_ENTRYPOINTS
   libc.src.grp.getgrouplist
   libc.src.grp.getgrnam
   libc.src.grp.getgrnam_r
+  libc.src.grp.initgroups
   libc.src.grp.setgrent
+  libc.src.grp.setgroups
 
   # pwd.h entrypoints
   libc.src.pwd.endpwent
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 7e7e90ff0e929..eded9fe955ccf 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -65,7 +65,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.grp.getgrouplist
     libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
+    libc.src.grp.initgroups
     libc.src.grp.setgrent
+    libc.src.grp.setgroups
 
     # net/if.h entrypoints
     libc.src.net.if_indextoname
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index e3facf92e81de..11c1093419ce8 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -65,7 +65,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.grp.getgrouplist
     libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
+    libc.src.grp.initgroups
     libc.src.grp.setgrent
+    libc.src.grp.setgroups
 
     # net/if.h entrypoints
     libc.src.net.if_indextoname
diff --git a/libc/include/grp.yaml b/libc/include/grp.yaml
index 757c69d006533..a4c9cbc78bc75 100644
--- a/libc/include/grp.yaml
+++ b/libc/include/grp.yaml
@@ -59,8 +59,22 @@ functions:
       - type: gid_t
       - type: gid_t *
       - type: int *
+  - name: initgroups
+    standards:
+      - gnu
+    return_type: int
+    arguments:
+      - type: const char *
+      - type: gid_t
   - name: setgrent
     standards:
       - posix
     return_type: void
     arguments: []
+  - name: setgroups
+    standards:
+      - gnu
+    return_type: int
+    arguments:
+      - type: size_t
+      - type: const gid_t *
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 1af11116862c7..2a24b6b28cab0 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -879,6 +879,20 @@ add_header_library(
     libc.src.__support.OSUtil.osutil
 )
 
+add_header_library(
+  setgroups
+  HDRS
+    setgroups.h
+  DEPENDS
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.include.sys_syscall
+    libc.src.__support.common
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+    libc.src.__support.OSUtil.osutil
+)
+
 add_header_library(
   setpgid
   HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/setgroups.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/setgroups.h
new file mode 100644
index 0000000000000..c316d64623fff
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/setgroups.h
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Syscall wrapper for setgroups.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_SETGROUPS_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_SETGROUPS_H
+
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> setgroups(size_t size, const gid_t *list) {
+  // Confusingly, 32-bit is the newer syscall, replacing the 16-bit legacy
+  // version.
+#if defined(SYS_setgroups32)
+  return syscall_checked<int>(SYS_setgroups32, size, list);
+#elif defined(SYS_setgroups)
+  return syscall_checked<int>(SYS_setgroups, size, list);
+#else
+#error "SYS_setgroups and SYS_setgroups32 syscalls not available."
+#endif
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_SETGROUPS_H
diff --git a/libc/src/grp/CMakeLists.txt b/libc/src/grp/CMakeLists.txt
index 85ad797b00e24..f7a2c81eea181 100644
--- a/libc/src/grp/CMakeLists.txt
+++ b/libc/src/grp/CMakeLists.txt
@@ -116,6 +116,22 @@ add_entrypoint_object(
     .grp_utils
 )
 
+add_entrypoint_object(
+  initgroups
+  SRCS
+    initgroups.cpp
+  HDRS
+    initgroups.h
+  DEPENDS
+    libc.hdr.types.gid_t
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.errno.errno
+    .grp_utils
+)
+
 add_entrypoint_object(
   setgrent
   SRCS
@@ -130,6 +146,23 @@ add_entrypoint_object(
     .grp_utils
 )
 
+add_entrypoint_object(
+  setgroups
+  SRCS
+    setgroups.cpp
+  HDRS
+    setgroups.h
+  DEPENDS
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.src.__support.OSUtil.linux.syscall_wrappers.setgroups
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.errno.errno
+)
+
 add_object_library(
   grp_utils
   HDRS
@@ -151,6 +184,7 @@ add_object_library(
     libc.src.__support.CPP.string_view
     libc.src.__support.File.file
     libc.src.__support.File.platform_file
+    libc.src.__support.OSUtil.linux.syscall_wrappers.setgroups
     libc.src.__support.ctype_utils
     libc.src.__support.error_or
     libc.src.__support.libc_assert
diff --git a/libc/src/grp/grp_utils.cpp b/libc/src/grp/grp_utils.cpp
index 771e4c0c721a4..31bcd9f601cb2 100644
--- a/libc/src/grp/grp_utils.cpp
+++ b/libc/src/grp/grp_utils.cpp
@@ -24,6 +24,7 @@
 #include "src/__support/CPP/limits.h"
 #include "src/__support/CPP/span.h"
 #include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/setgroups.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/error_or.h"
 #include "src/__support/libc_assert.h"
@@ -266,12 +267,56 @@ class GidList {
 
   [[nodiscard]] LIBC_INLINE size_t size() const { return count; }
 
+  [[nodiscard]] LIBC_INLINE const gid_t *data() const { return buf; }
+
   [[nodiscard]] LIBC_INLINE gid_t operator[](size_t i) const {
     LIBC_ASSERT(i < count);
     return buf[i];
   }
 };
 
+ErrorOr<void> populate_gid_list(cpp::string_view user, gid_t group,
+                                GidList &gid_list, const char *path) {
+  if (!gid_list.push_back(group))
+    return Error(ENOMEM);
+
+  pwd::ScopedFlatFileDatabase<struct group> local_db(path ? path
+                                                          : group_file_path);
+  pwd::ScopedDynamicBuffer buffer;
+  struct group entry = {};
+
+  const auto open_res = local_db.setdb();
+  if (open_res.has_value()) {
+    while (true) {
+      const auto next_res = local_db.getnext(&entry, buffer);
+      if (!next_res.has_value()) {
+        if (next_res.error() == ENOMEM)
+          return Error(ENOMEM);
+        break;
+      }
+      if (!next_res.value())
+        break;
+
+      bool is_member = false;
+      if (entry.gr_mem) {
+        for (char **m = entry.gr_mem; *m != nullptr; ++m) {
+          if (cpp::string_view(*m) == user) {
+            is_member = true;
+            break;
+          }
+        }
+      }
+
+      if (is_member && !gid_list.contains(entry.gr_gid)) {
+        if (!gid_list.push_back(entry.gr_gid))
+          return Error(ENOMEM);
+      }
+    }
+  }
+
+  return {};
+}
+
 } // namespace
 
 void TESTONLY_set_group_path(const char *path) {
@@ -334,42 +379,9 @@ ErrorOr<size_t> get_group_list(cpp::string_view user, gid_t group,
                                gid_t *groups, size_t ngroups,
                                const char *path) {
   GidList gid_list;
-  if (!gid_list.push_back(group))
-    return Error(ENOMEM);
-
-  pwd::ScopedFlatFileDatabase<struct group> local_db(path ? path
-                                                          : group_file_path);
-  pwd::ScopedDynamicBuffer buffer;
-  struct group entry = {};
-
-  const auto open_res = local_db.setdb();
-  if (open_res.has_value()) {
-    while (true) {
-      const auto next_res = local_db.getnext(&entry, buffer);
-      if (!next_res.has_value()) {
-        if (next_res.error() == ENOMEM)
-          return Error(ENOMEM);
-        break;
-      }
-      if (!next_res.value())
-        break;
-
-      bool is_member = false;
-      if (entry.gr_mem) {
-        for (char **m = entry.gr_mem; *m != nullptr; ++m) {
-          if (cpp::string_view(*m) == user) {
-            is_member = true;
-            break;
-          }
-        }
-      }
-
-      if (is_member && !gid_list.contains(entry.gr_gid)) {
-        if (!gid_list.push_back(entry.gr_gid))
-          return Error(ENOMEM);
-      }
-    }
-  }
+  const auto res = populate_gid_list(user, group, gid_list, path);
+  if (!res.has_value())
+    return Error(res.error());
 
   const size_t copy_count =
       ngroups < gid_list.size() ? ngroups : gid_list.size();
@@ -379,5 +391,14 @@ ErrorOr<size_t> get_group_list(cpp::string_view user, gid_t group,
   return gid_list.size();
 }
 
+ErrorOr<int> init_groups(cpp::string_view user, gid_t group, const char *path) {
+  GidList gid_list;
+  const auto res = populate_gid_list(user, group, gid_list, path);
+  if (!res.has_value())
+    return Error(res.error());
+
+  return linux_syscalls::setgroups(gid_list.size(), gid_list.data());
+}
+
 } // namespace grp
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/grp/grp_utils.h b/libc/src/grp/grp_utils.h
index 70f85791ef79e..69ff3bb306dfb 100644
--- a/libc/src/grp/grp_utils.h
+++ b/libc/src/grp/grp_utils.h
@@ -61,6 +61,11 @@ ErrorOr<size_t> get_group_list(cpp::string_view user, gid_t group,
                                gid_t *groups, size_t ngroups,
                                const char *path = nullptr);
 
+// Sets the supplementary group access list for user, including the specified
+// group ID, by reading the group database.
+ErrorOr<int> init_groups(cpp::string_view user, gid_t group,
+                         const char *path = nullptr);
+
 } // namespace grp
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/grp/initgroups.cpp b/libc/src/grp/initgroups.cpp
new file mode 100644
index 0000000000000..83211b81497b7
--- /dev/null
+++ b/libc/src/grp/initgroups.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of initgroups.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/initgroups.h"
+#include "hdr/types/gid_t.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, initgroups, (const char *user, gid_t group)) {
+  LIBC_CRASH_ON_NULLPTR(user);
+
+  const auto res = grp::init_groups(user, group);
+  if (!res.has_value()) {
+    libc_errno = res.error();
+    return -1;
+  }
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/grp/initgroups.h b/libc/src/grp/initgroups.h
new file mode 100644
index 0000000000000..9a7f98f0171f9
--- /dev/null
+++ b/libc/src/grp/initgroups.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Header file for initgroups function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_INITGROUPS_H
+#define LLVM_LIBC_SRC_GRP_INITGROUPS_H
+
+#include "hdr/types/gid_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int initgroups(const char *user, gid_t group);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_INITGROUPS_H
diff --git a/libc/src/grp/setgroups.cpp b/libc/src/grp/setgroups.cpp
new file mode 100644
index 0000000000000..098cdc5ad6315
--- /dev/null
+++ b/libc/src/grp/setgroups.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of setgroups.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/setgroups.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/setgroups.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, setgroups, (size_t size, const gid_t *list)) {
+  if (size > 0)
+    LIBC_CRASH_ON_NULLPTR(list);
+
+  const auto ret = linux_syscalls::setgroups(size, list);
+  if (!ret) {
+    libc_errno = ret.error();
+    return -1;
+  }
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/grp/setgroups.h b/libc/src/grp/setgroups.h
new file mode 100644
index 0000000000000..0bcf502397a81
--- /dev/null
+++ b/libc/src/grp/setgroups.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Header file for setgroups function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_SETGROUPS_H
+#define LLVM_LIBC_SRC_GRP_SETGROUPS_H
+
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int setgroups(size_t size, const gid_t *list);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_SETGROUPS_H
diff --git a/libc/test/src/grp/CMakeLists.txt b/libc/test/src/grp/CMakeLists.txt
index 32c532c917a6d..e05586fd8bdc9 100644
--- a/libc/test/src/grp/CMakeLists.txt
+++ b/libc/test/src/grp/CMakeLists.txt
@@ -179,3 +179,48 @@ add_libc_test(
     libc.test.UnitTest.ErrnoCheckingTest
     libc.test.UnitTest.ErrnoSetterMatcher
 )
+
+add_libc_test(
+  initgroups_test
+  SUITE
+    libc_grp_unittests
+  HDRS
+    grp_test_utils.h
+  SRCS
+    initgroups_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.signal_macros
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_group
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.grp_utils
+    libc.src.grp.initgroups
+    libc.src.grp.setgrent
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+    libc.src.unistd.getuid
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+)
+
+add_libc_test(
+  setgroups_test
+  SUITE
+    libc_grp_unittests
+  SRCS
+    setgroups_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.signal_macros
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.src.grp.setgroups
+    libc.src.unistd.getuid
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+)
diff --git a/libc/test/src/grp/initgroups_test.cpp b/libc/test/src/grp/initgroups_test.cpp
new file mode 100644
index 0000000000000..a024cab96fc05
--- /dev/null
+++ b/libc/test/src/grp/initgroups_test.cpp
@@ -0,0 +1,102 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for initgroups.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/grp/endgrent.h"
+#include "src/grp/getgrent.h"
+#include "src/grp/grp_utils.h"
+#include "src/grp/initgroups.h"
+#include "src/grp/setgrent.h"
+#include "src/unistd/getuid.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/grp/grp_test_utils.h"
+
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+TEST_F(LlvmLibcGrpTest, InitgroupsPrivilegeCheck) {
+  const char *content = "root:x:0:root\n"
+                        "users:x:100:user1,user2\n"
+                        "admins:x:300:user1\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("initgroups_priv.test"),
+                            content);
+
+  if (LIBC_NAMESPACE::getuid() == 0) {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("user1", 1000), Succeeds(0));
+  } else {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("user1", 1000), Fails(EPERM));
+  }
+}
+
+TEST_F(LlvmLibcGrpTest, InitgroupsNonexistentUser) {
+  const char *content = "wheel:x:10:root\n";
+  ScopedGroupFile test_file(
+      libc_make_test_file_path("initgroups_nonexist.test"), content);
+
+  if (LIBC_NAMESPACE::getuid() == 0) {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("nonexistent", 1000), Succeeds(0));
+  } else {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("nonexistent", 1000), Fails(EPERM));
+  }
+}
+
+TEST_F(LlvmLibcGrpTest, InitgroupsNonexistentFile) {
+  LIBC_NAMESPACE::grp::TESTONLY_set_group_path(
+      libc_make_test_file_path("initgroups_missing.test"));
+
+  if (LIBC_NAMESPACE::getuid() == 0) {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("anyuser", 1000), Succeeds(0));
+  } else {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("anyuser", 1000), Fails(EPERM));
+  }
+}
+
+TEST_F(LlvmLibcGrpTest, InitgroupsDoesNotDisturbIteration) {
+  const char *content = "group1:x:1:user1\n"
+                        "group2:x:2:user2\n"
+                        "group3:x:3:user1\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("initgroups_iter.test"),
+                            content);
+
+  LIBC_NAMESPACE::setgrent();
+  const auto first = LIBC_NAMESPACE::grp::read_next();
+  ASSERT_TRUE(first.has_value());
+  ASSERT_NE(first.value(), nullptr);
+  EXPECT_STREQ(first.value()->gr_name, "group1");
+
+  // initgroups reads the database using a scoped stream.
+  if (LIBC_NAMESPACE::getuid() == 0) {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("user2", 50), Succeeds(0));
+  } else {
+    EXPECT_THAT(LIBC_NAMESPACE::initgroups("user2", 50), Fails(EPERM));
+  }
+
+  const auto second = LIBC_NAMESPACE::grp::read_next();
+  ASSERT_TRUE(second.has_value());
+  ASSERT_NE(second.value(), nullptr);
+  EXPECT_STREQ(second.value()->gr_name, "group2");
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcGrpTest, NullUserCrash) {
+  ASSERT_DEATH([] { LIBC_NAMESPACE::initgroups(nullptr, 1000); },
+               WITH_SIGNAL(-1));
+}
+#endif // LIBC_ADD_NULL_CHECKS
diff --git a/libc/test/src/grp/setgroups_test.cpp b/libc/test/src/grp/setgroups_test.cpp
new file mode 100644
index 0000000000000..c17f0029e2199
--- /dev/null
+++ b/libc/test/src/grp/setgroups_test.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for setgroups.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "src/grp/setgroups.h"
+#include "src/unistd/getuid.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::any_of;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+using LlvmLibcSetgroupsTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcSetgroupsTest, InvalidSizeReturnsEinval) {
+  gid_t list[1] = {0};
+  EXPECT_THAT(LIBC_NAMESPACE::setgroups(static_cast<size_t>(-1), list),
+              Fails(any_of(EINVAL, EPERM)));
+}
+
+TEST_F(LlvmLibcSetgroupsTest, PrivilegeCheck) {
+  gid_t list[1] = {1000};
+  if (LIBC_NAMESPACE::getuid() == 0) {
+    EXPECT_THAT(LIBC_NAMESPACE::setgroups(1, list), Succeeds(0));
+  } else {
+    EXPECT_THAT(LIBC_NAMESPACE::setgroups(1, list), Fails(EPERM));
+  }
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcSetgroupsTest, NullPointerCrash) {
+  ASSERT_DEATH([] { LIBC_NAMESPACE::setgroups(1, nullptr); }, WITH_SIGNAL(-1));
+}
+#endif // LIBC_ADD_NULL_CHECKS



More information about the libc-commits mailing list