[libc-commits] [libc] 6f653e2 - [libc] Add getgrent, setgrent, and endgrent entrypoints (#224372)

via libc-commits libc-commits at lists.llvm.org
Fri Sep 18 02:23:03 PDT 2026


Author: Jeff Bailey
Date: 2026-09-18T10:22:56+01:00
New Revision: 6f653e2e40ef7095b5cb04cd0b34d8d5b754301b

URL: https://github.com/llvm/llvm-project/commit/6f653e2e40ef7095b5cb04cd0b34d8d5b754301b
DIFF: https://github.com/llvm/llvm-project/commit/6f653e2e40ef7095b5cb04cd0b34d8d5b754301b.diff

LOG: [libc] Add getgrent, setgrent, and endgrent entrypoints (#224372)

Implement the POSIX group database iteration entrypoints getgrent,
setgrent, and endgrent on top of grp_utils and FlatFileDatabase.

Non-reentrant calls read into a process-global DynamicBuffer that grows
to the high-water mark of the largest group record seen. endgrent closes
the file stream without freeing the buffer so pointers returned prior to
endgrent remain valid until the next non-reentrant call.

* Add getgrent, setgrent, and endgrent entrypoints
* Define getgrent, setgrent, and endgrent in include/grp.yaml
* Expose entrypoints across Linux target architectures
* Add hermetic integration tests in test/src/grp/getgrent_test.cpp

Assisted-by: Automated tooling, human reviewed.

Added: 
    libc/src/grp/endgrent.cpp
    libc/src/grp/endgrent.h
    libc/src/grp/getgrent.cpp
    libc/src/grp/getgrent.h
    libc/src/grp/setgrent.cpp
    libc/src/grp/setgrent.h
    libc/test/src/grp/getgrent_test.cpp
    libc/test/src/grp/grp_test_utils.h

Modified: 
    libc/config/linux/aarch64/entrypoints.txt
    libc/config/linux/arm/entrypoints.txt
    libc/config/linux/i386/entrypoints.txt
    libc/config/linux/riscv/entrypoints.txt
    libc/config/linux/x86_64/entrypoints.txt
    libc/include/grp.yaml
    libc/src/grp/CMakeLists.txt
    libc/test/src/grp/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 1a05ed55365b4..4de8ce85b0569 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -44,6 +44,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.fcntl.posix_fadvise
     libc.src.fcntl.posix_fadvise64
 
+    # grp.h entrypoints
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.setgrent
+
     # poll.h entrypoints
     libc.src.poll.poll
 

diff  --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index e32430cac7c8c..f4347331d0b31 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -30,6 +30,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     # errno.h entrypoints
     libc.src.errno.errno
 
+    # grp.h entrypoints
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.setgrent
+
     # poll.h entrypoints
     libc.src.poll.poll
 

diff  --git a/libc/config/linux/i386/entrypoints.txt b/libc/config/linux/i386/entrypoints.txt
index 4766ea1522cae..f2b7dcca42c08 100644
--- a/libc/config/linux/i386/entrypoints.txt
+++ b/libc/config/linux/i386/entrypoints.txt
@@ -2,6 +2,11 @@ set(TARGET_LIBC_ENTRYPOINTS
   # errno.h entrypoints
   libc.src.errno.errno
 
+  # grp.h entrypoints
+  libc.src.grp.endgrent
+  libc.src.grp.getgrent
+  libc.src.grp.setgrent
+
   # pwd.h entrypoints
   libc.src.pwd.endpwent
   libc.src.pwd.getpwent

diff  --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index ba3600966fabf..889b4c5c79dbb 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -55,6 +55,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.fcntl.posix_fadvise
     libc.src.fcntl.posix_fadvise64
 
+    # grp.h entrypoints
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.setgrent
+
     # net/if.h entrypoints
     libc.src.net.if_indextoname
     libc.src.net.if_nametoindex

diff  --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a32e83236e277..8e748287a2469 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -55,6 +55,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.fcntl.posix_fadvise
     libc.src.fcntl.posix_fadvise64
 
+    # grp.h entrypoints
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.setgrent
+
     # net/if.h entrypoints
     libc.src.net.if_indextoname
     libc.src.net.if_nametoindex

diff  --git a/libc/include/grp.yaml b/libc/include/grp.yaml
index 015d9c46e195f..419c1212fba3c 100644
--- a/libc/include/grp.yaml
+++ b/libc/include/grp.yaml
@@ -7,4 +7,19 @@ types:
   - type_name: size_t
 enums: []
 objects: []
-functions: []
+functions:
+  - name: endgrent
+    standards:
+      - posix
+    return_type: void
+    arguments: []
+  - name: getgrent
+    standards:
+      - posix
+    return_type: struct group *
+    arguments: []
+  - name: setgrent
+    standards:
+      - posix
+    return_type: void
+    arguments: []

diff  --git a/libc/src/grp/CMakeLists.txt b/libc/src/grp/CMakeLists.txt
index 9e460e376a14f..67f3b900f55d8 100644
--- a/libc/src/grp/CMakeLists.txt
+++ b/libc/src/grp/CMakeLists.txt
@@ -3,6 +3,49 @@ if(NOT TARGET libc.src.__support.File.file OR
   return()
 endif()
 
+add_entrypoint_object(
+  endgrent
+  SRCS
+    endgrent.cpp
+  HDRS
+    endgrent.h
+  DEPENDS
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.errno.errno
+    .grp_utils
+)
+
+add_entrypoint_object(
+  getgrent
+  SRCS
+    getgrent.cpp
+  HDRS
+    getgrent.h
+  DEPENDS
+    libc.hdr.types.struct_group
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.errno.errno
+    .grp_utils
+)
+
+add_entrypoint_object(
+  setgrent
+  SRCS
+    setgrent.cpp
+  HDRS
+    setgrent.h
+  DEPENDS
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.errno.errno
+    .grp_utils
+)
+
 add_object_library(
   grp_utils
   HDRS

diff  --git a/libc/src/grp/endgrent.cpp b/libc/src/grp/endgrent.cpp
new file mode 100644
index 0000000000000..0af51d7970b8b
--- /dev/null
+++ b/libc/src/grp/endgrent.cpp
@@ -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
+/// Implementation of endgrent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/endgrent.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, endgrent, ()) {
+  auto res = grp::close();
+  if (!res.has_value())
+    libc_errno = res.error();
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/grp/endgrent.h b/libc/src/grp/endgrent.h
new file mode 100644
index 0000000000000..99424e03c8ede
--- /dev/null
+++ b/libc/src/grp/endgrent.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 endgrent function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_ENDGRENT_H
+#define LLVM_LIBC_SRC_GRP_ENDGRENT_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void endgrent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_ENDGRENT_H

diff  --git a/libc/src/grp/getgrent.cpp b/libc/src/grp/getgrent.cpp
new file mode 100644
index 0000000000000..b17b1a4b66a27
--- /dev/null
+++ b/libc/src/grp/getgrent.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getgrent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/getgrent.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct group *, getgrent, ()) {
+  auto res = grp::read_next();
+  if (!res.has_value()) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/grp/getgrent.h b/libc/src/grp/getgrent.h
new file mode 100644
index 0000000000000..b784bcb82a13c
--- /dev/null
+++ b/libc/src/grp/getgrent.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 getgrent function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_GETGRENT_H
+#define LLVM_LIBC_SRC_GRP_GETGRENT_H
+
+#include "hdr/types/struct_group.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct group *getgrent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_GETGRENT_H

diff  --git a/libc/src/grp/setgrent.cpp b/libc/src/grp/setgrent.cpp
new file mode 100644
index 0000000000000..196b24d569482
--- /dev/null
+++ b/libc/src/grp/setgrent.cpp
@@ -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
+/// Implementation of setgrent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/setgrent.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, setgrent, ()) {
+  auto res = grp::open();
+  if (!res.has_value())
+    libc_errno = res.error();
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/grp/setgrent.h b/libc/src/grp/setgrent.h
new file mode 100644
index 0000000000000..32484cc611080
--- /dev/null
+++ b/libc/src/grp/setgrent.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 setgrent function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_SETGRENT_H
+#define LLVM_LIBC_SRC_GRP_SETGRENT_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void setgrent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_SETGRENT_H

diff  --git a/libc/test/src/grp/CMakeLists.txt b/libc/test/src/grp/CMakeLists.txt
index dbc1ac765455b..537bb02a53db2 100644
--- a/libc/test/src/grp/CMakeLists.txt
+++ b/libc/test/src/grp/CMakeLists.txt
@@ -19,3 +19,31 @@ add_libc_test(
     libc.src.__support.pwd.flat_file_db
     libc.src.grp.grp_utils
 )
+
+add_libc_test(
+  getgrent_test
+  SUITE
+    libc_grp_unittests
+  HDRS
+    grp_test_utils.h
+  SRCS
+    getgrent_test.cpp
+  DEPENDS
+    libc.hdr.errno_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.__support.ctype_utils
+    libc.src.__support.libc_errno
+    libc.src.errno.errno
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.grp_utils
+    libc.src.grp.setgrent
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+)

diff  --git a/libc/test/src/grp/getgrent_test.cpp b/libc/test/src/grp/getgrent_test.cpp
new file mode 100644
index 0000000000000..48e70c3702cf0
--- /dev/null
+++ b/libc/test/src/grp/getgrent_test.cpp
@@ -0,0 +1,212 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getgrent, setgrent, and endgrent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/libc_errno.h"
+#include "src/grp/endgrent.h"
+#include "src/grp/getgrent.h"
+#include "src/grp/grp_utils.h"
+#include "src/grp/setgrent.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/grp/grp_test_utils.h"
+
+using LIBC_NAMESPACE::libc_errno;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+
+TEST_F(LlvmLibcGrpTest, GetGrentTestSuccess) {
+  constexpr char CONTENT[] = "root:x:0:root\n"
+                             "bin:x:1:bin,daemon\n"
+                             "wheel:x:10:root,admin,user1\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrent_success.test"),
+                            CONTENT);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+  EXPECT_STREQ(grp->gr_passwd, "x");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(0));
+  ASSERT_NE(grp->gr_mem, nullptr);
+  EXPECT_STREQ(grp->gr_mem[0], "root");
+  EXPECT_EQ(grp->gr_mem[1], nullptr);
+
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "bin");
+  EXPECT_STREQ(grp->gr_passwd, "x");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(1));
+  ASSERT_NE(grp->gr_mem, nullptr);
+  EXPECT_STREQ(grp->gr_mem[0], "bin");
+  EXPECT_STREQ(grp->gr_mem[1], "daemon");
+  EXPECT_EQ(grp->gr_mem[2], nullptr);
+
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "wheel");
+  EXPECT_STREQ(grp->gr_passwd, "x");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(10));
+  ASSERT_NE(grp->gr_mem, nullptr);
+  EXPECT_STREQ(grp->gr_mem[0], "root");
+  EXPECT_STREQ(grp->gr_mem[1], "admin");
+  EXPECT_STREQ(grp->gr_mem[2], "user1");
+  EXPECT_EQ(grp->gr_mem[3], nullptr);
+
+  // At EOF, getgrent returns nullptr without altering errno.
+  libc_errno = 0;
+  grp = LIBC_NAMESPACE::getgrent();
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_SUCCESS();
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrentTestFailure) {
+  constexpr char CONTENT[] = "malformed_line_without_colons\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrent_failure.test"),
+                            CONTENT);
+
+  ASSERT_THAT(reinterpret_cast<void *>(LIBC_NAMESPACE::getgrent()),
+              Fails(EINVAL, static_cast<void *>(nullptr)));
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+TEST_F(LlvmLibcGrpTest, SetGrentTestHermetic) {
+  constexpr char CONTENT[] = "root:x:0:root\n"
+                             "bin:x:1:bin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("setgrent_hermetic.test"),
+                            CONTENT);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  // Rewind the group database stream to the beginning.
+  LIBC_NAMESPACE::setgrent();
+
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+TEST_F(LlvmLibcGrpTest, ReopenAfterEndgrent) {
+  constexpr char CONTENT[] = "root:x:0:root\n"
+                             "bin:x:1:bin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("reopen_after_end.test"),
+                            CONTENT);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  LIBC_NAMESPACE::endgrent();
+
+  // endgrent closes the stream without invalidating the last returned pointer.
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  // Subsequent getgrent after endgrent reopens the database from the start.
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+TEST_F(LlvmLibcGrpTest, FileOpenFailure) {
+  auto nonexistent_path = libc_make_test_file_path("getgrent_nonexistent.test");
+  LIBC_NAMESPACE::grp::TESTONLY_set_group_path(nonexistent_path);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_EQ(ENOENT);
+
+  // POSIX specifies that setgrent() sets errno on error.
+  libc_errno = 0;
+  LIBC_NAMESPACE::setgrent();
+  ASSERT_ERRNO_EQ(ENOENT);
+}
+
+TEST_F(LlvmLibcGrpTest, BlankLines) {
+  constexpr char CONTENT[] = "\n"
+                             "root:x:0:root\n"
+                             "\n"
+                             "\n"
+                             "bin:x:1:bin\n"
+                             "\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrent_blank.test"),
+                            CONTENT);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "bin");
+
+  // POSIX mandates that getgrent shall return null and not change errno on EOF.
+  libc_errno = ENOENT;
+  grp = LIBC_NAMESPACE::getgrent();
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_EQ(ENOENT);
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+TEST_F(LlvmLibcGrpTest, DynamicMemberAndLineGrowth) {
+  // Construct a record with 600 members (~3 KB), exercising both dynamic line
+  // growth and per-record sizing of the gr_mem pointer array.
+  constexpr size_t MEMBER_COUNT = 600;
+  constexpr size_t FILE_BUFFER_SIZE = 8192;
+  char content[FILE_BUFFER_SIZE];
+  size_t offset = 0;
+  constexpr char HEADER[] = "biggroup:x:2000:";
+  for (size_t i = 0; HEADER[i] != '\0'; ++i)
+    content[offset++] = HEADER[i];
+
+  for (size_t i = 0; i < MEMBER_COUNT; ++i) {
+    if (i > 0)
+      content[offset++] = ',';
+    content[offset++] = 'u';
+    content[offset++] =
+        LIBC_NAMESPACE::internal::int_to_b36_char((i / 100) % 10);
+    content[offset++] =
+        LIBC_NAMESPACE::internal::int_to_b36_char((i / 10) % 10);
+    content[offset++] = LIBC_NAMESPACE::internal::int_to_b36_char(i % 10);
+  }
+  content[offset++] = '\n';
+  content[offset] = '\0';
+
+  ScopedGroupFile test_file(
+      libc_make_test_file_path("getgrent_dynamic_growth.test"), content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "biggroup");
+  EXPECT_STREQ(grp->gr_passwd, "x");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(2000));
+  ASSERT_NE(grp->gr_mem, nullptr);
+
+  EXPECT_STREQ(grp->gr_mem[0], "u000");
+  EXPECT_STREQ(grp->gr_mem[MEMBER_COUNT / 2], "u300");
+  EXPECT_STREQ(grp->gr_mem[MEMBER_COUNT - 1], "u599");
+  EXPECT_EQ(grp->gr_mem[MEMBER_COUNT], nullptr);
+
+  LIBC_NAMESPACE::endgrent();
+}

diff  --git a/libc/test/src/grp/grp_test_utils.h b/libc/test/src/grp/grp_test_utils.h
new file mode 100644
index 0000000000000..dad4c13449833
--- /dev/null
+++ b/libc/test/src/grp/grp_test_utils.h
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Shared test utilities and fixtures for grp tests.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TEST_SRC_GRP_GRP_TEST_UTILS_H
+#define LLVM_LIBC_TEST_SRC_GRP_GRP_TEST_UTILS_H
+
+#include "hdr/types/size_t.h"
+#include "src/__support/File/file.h"
+#include "src/grp/grp_utils.h"
+#include "src/stdio/remove.h"
+#include "src/string/string_utils.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+// RAII helper class for creating and automatically removing temporary test
+// files, while safely scoping the group database path.
+class ScopedGroupFile {
+  char path[256];
+
+public:
+  ScopedGroupFile(const ScopedGroupFile &) = delete;
+  ScopedGroupFile &operator=(const ScopedGroupFile &) = delete;
+
+  ScopedGroupFile(const char *file_path, const char *content) {
+    LIBC_NAMESPACE::internal::strlcpy(path, file_path, sizeof(path));
+
+    auto file_or = LIBC_NAMESPACE::openfile(path, "w");
+    if (file_or.has_value()) {
+      auto *f = file_or.value();
+      size_t len = LIBC_NAMESPACE::internal::string_length(content);
+      f->write(content, len);
+      f->close();
+    }
+    LIBC_NAMESPACE::grp::TESTONLY_set_group_path(path);
+  }
+
+  ~ScopedGroupFile() {
+    LIBC_NAMESPACE::grp::TESTONLY_reset_group_path();
+    LIBC_NAMESPACE::remove(path);
+  }
+};
+
+// Base test fixture that resets the group database path and validates errno.
+class LlvmLibcGrpTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+protected:
+  void TearDown() override {
+    LIBC_NAMESPACE::grp::TESTONLY_reset_group_path();
+    ErrnoCheckingTest::TearDown();
+  }
+};
+
+#endif // LLVM_LIBC_TEST_SRC_GRP_GRP_TEST_UTILS_H


        


More information about the libc-commits mailing list