[libc-commits] [libc] [libc] Add getgrnam and getgrgid entrypoints (PR #224858)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Sat Sep 19 13:49:13 PDT 2026


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

Add the non-reentrant group database lookup entrypoints getgrnam and getgrgid.

Both entrypoints delegate to grp::find_by_name and grp::find_by_gid, sharing the single static DynamicBuffer and struct group with getgrent as permitted by POSIX. Because endgrent closes the file stream without freeing the shared buffer, calling endgrent does not invalidate pointers returned by getgrnam or getgrgid.

* Add getgrnam and getgrgid entrypoints
* Add non-reentrant find_by_name and find_by_gid overloads in grp_utils
* Define getgrnam and getgrgid in include/grp.yaml
* Add hermetic unit tests for getgrnam and getgrgid

Assisted-by: Automated tooling, human reviewed.

>From f9b0b2f8777d1f9bc8474e659318c04527b25381 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Mon, 14 Sep 2026 08:31:44 +0100
Subject: [PATCH] [libc] Add getgrnam and getgrgid entrypoints

Add the non-reentrant group database lookup entrypoints getgrnam and
getgrgid.

Both entrypoints delegate to grp::find_by_name and grp::find_by_gid,
sharing the single static DynamicBuffer and struct group with getgrent
as permitted by POSIX. Because endgrent closes the file stream without
freeing the shared buffer, calling endgrent does not invalidate pointers
returned by getgrnam or getgrgid.

* Add getgrnam and getgrgid entrypoints
* Add non-reentrant find_by_name and find_by_gid overloads in grp_utils
* Define getgrnam and getgrgid in include/grp.yaml
* Add hermetic unit tests for getgrnam and getgrgid

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                     |  12 ++
 libc/src/grp/CMakeLists.txt               |  33 ++++
 libc/src/grp/getgrgid.cpp                 |  32 ++++
 libc/src/grp/getgrgid.h                   |  27 +++
 libc/src/grp/getgrnam.cpp                 |  35 ++++
 libc/src/grp/getgrnam.h                   |  26 +++
 libc/src/grp/grp_utils.cpp                |  65 +++++--
 libc/src/grp/grp_utils.h                  |   3 +
 libc/test/src/grp/CMakeLists.txt          |  57 +++++++
 libc/test/src/grp/getgrgid_test.cpp       | 178 +++++++++++++++++++
 libc/test/src/grp/getgrnam_test.cpp       | 197 ++++++++++++++++++++++
 16 files changed, 660 insertions(+), 15 deletions(-)
 create mode 100644 libc/src/grp/getgrgid.cpp
 create mode 100644 libc/src/grp/getgrgid.h
 create mode 100644 libc/src/grp/getgrnam.cpp
 create mode 100644 libc/src/grp/getgrnam.h
 create mode 100644 libc/test/src/grp/getgrgid_test.cpp
 create mode 100644 libc/test/src/grp/getgrnam_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index f55ac4b16cfd26..96b23e6716edcf 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -48,7 +48,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid
     libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index 1602255c6559cc..1241c1b45e0ddc 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -33,7 +33,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid
     libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
diff --git a/libc/config/linux/i386/entrypoints.txt b/libc/config/linux/i386/entrypoints.txt
index d5ea487b48f5eb..b58956c1666869 100644
--- a/libc/config/linux/i386/entrypoints.txt
+++ b/libc/config/linux/i386/entrypoints.txt
@@ -5,7 +5,9 @@ set(TARGET_LIBC_ENTRYPOINTS
   # grp.h entrypoints
   libc.src.grp.endgrent
   libc.src.grp.getgrent
+  libc.src.grp.getgrgid
   libc.src.grp.getgrgid_r
+  libc.src.grp.getgrnam
   libc.src.grp.getgrnam_r
   libc.src.grp.setgrent
 
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 921de1c649fb42..0149b5107caa8a 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -59,7 +59,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid
     libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 165484987f16d7..69fb99b049cfe7 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -59,7 +59,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid
     libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam
     libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
diff --git a/libc/include/grp.yaml b/libc/include/grp.yaml
index e1ace37cb9b296..6399cd27d11011 100644
--- a/libc/include/grp.yaml
+++ b/libc/include/grp.yaml
@@ -18,6 +18,12 @@ functions:
       - posix
     return_type: struct group *
     arguments: []
+  - name: getgrgid
+    standards:
+      - posix
+    return_type: struct group *
+    arguments:
+      - type: gid_t
   - name: getgrgid_r
     standards:
       - posix
@@ -28,6 +34,12 @@ functions:
       - type: char *
       - type: size_t
       - type: struct group **
+  - name: getgrnam
+    standards:
+      - posix
+    return_type: struct group *
+    arguments:
+      - type: const char *
   - name: getgrnam_r
     standards:
       - posix
diff --git a/libc/src/grp/CMakeLists.txt b/libc/src/grp/CMakeLists.txt
index 1fd7b2bd3d3df0..ee613e6f3217f4 100644
--- a/libc/src/grp/CMakeLists.txt
+++ b/libc/src/grp/CMakeLists.txt
@@ -32,6 +32,22 @@ add_entrypoint_object(
     .grp_utils
 )
 
+add_entrypoint_object(
+  getgrgid
+  SRCS
+    getgrgid.cpp
+  HDRS
+    getgrgid.h
+  DEPENDS
+    libc.hdr.types.gid_t
+    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(
   getgrgid_r
   SRCS
@@ -49,6 +65,23 @@ add_entrypoint_object(
     .grp_utils
 )
 
+add_entrypoint_object(
+  getgrnam
+  SRCS
+    getgrnam.cpp
+  HDRS
+    getgrnam.h
+  DEPENDS
+    libc.hdr.types.struct_group
+    libc.src.__support.CPP.string_view
+    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(
   getgrnam_r
   SRCS
diff --git a/libc/src/grp/getgrgid.cpp b/libc/src/grp/getgrgid.cpp
new file mode 100644
index 00000000000000..98896542db03fe
--- /dev/null
+++ b/libc/src/grp/getgrgid.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getgrgid.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/getgrgid.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/struct_group.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 *, getgrgid, (gid_t gid)) {
+  const auto res = grp::find_by_gid(gid);
+  if (!res.has_value()) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/grp/getgrgid.h b/libc/src/grp/getgrgid.h
new file mode 100644
index 00000000000000..6163f384defe10
--- /dev/null
+++ b/libc/src/grp/getgrgid.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 getgrgid function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_GETGRGID_H
+#define LLVM_LIBC_SRC_GRP_GETGRGID_H
+
+#include "hdr/types/gid_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct group *getgrgid(gid_t gid);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_GETGRGID_H
diff --git a/libc/src/grp/getgrnam.cpp b/libc/src/grp/getgrnam.cpp
new file mode 100644
index 00000000000000..0a1ca2608d2bde
--- /dev/null
+++ b/libc/src/grp/getgrnam.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 getgrnam.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/getgrnam.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/null_check.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct group *, getgrnam, (const char *name)) {
+  LIBC_CRASH_ON_NULLPTR(name);
+
+  const auto res = grp::find_by_name(name);
+  if (!res.has_value()) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return res.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/grp/getgrnam.h b/libc/src/grp/getgrnam.h
new file mode 100644
index 00000000000000..bc328fa5c7f266
--- /dev/null
+++ b/libc/src/grp/getgrnam.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 getgrnam function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_GETGRNAM_H
+#define LLVM_LIBC_SRC_GRP_GETGRNAM_H
+
+#include "hdr/types/struct_group.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct group *getgrnam(const char *name);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_GETGRNAM_H
diff --git a/libc/src/grp/grp_utils.cpp b/libc/src/grp/grp_utils.cpp
index f8cecb015461fb..00ebdaa561f1bd 100644
--- a/libc/src/grp/grp_utils.cpp
+++ b/libc/src/grp/grp_utils.cpp
@@ -168,14 +168,40 @@ LIBC_CONSTINIT pwd::FlatFileDatabase<struct group>
 // Note: These static buffers are process-global and NOT protected by a mutex
 // at this stage. POSIX getgrent is non-reentrant.
 //
-// A single static buffer is reused across non-reentrant group calls via
-// realloc, growing only to the high-water mark of the largest record seen.
-// endgrent() closes the file stream without freeing the buffer so that
+// A single static buffer and struct group are reused across getgrent,
+// getgrnam, and getgrgid per POSIX ("The return value may point to a static
+// area which is overwritten by a subsequent call to getgrent(), getgrgid(),
+// or getgrnam()"), growing only to the high-water mark of the largest record
+// seen. endgrent() closes the file stream without freeing the buffer so that
 // pointers returned prior to endgrent() remain valid until the next
 // non-reentrant call.
 LIBC_CONSTINIT pwd::DynamicBuffer line_buffer;
 LIBC_CONSTINIT struct group grp_entry = {};
 
+// The lookups are shared between the caller-supplied fixed buffer used by the
+// reentrant entrypoints and the process-global growable buffer used by the
+// non-reentrant ones. Both scan a scoped stream of their own so that a lookup
+// does not disturb an in-progress getgrent iteration.
+template <typename BufferType>
+ErrorOr<bool> lookup_by_name(cpp::string_view name, struct group *grp,
+                             BufferType &buffer, const char *path) {
+  pwd::ScopedFlatFileDatabase<struct group> local_db(path);
+  const auto matcher = [name](const struct group &entry) {
+    return cpp::string_view(entry.gr_name) == name;
+  };
+  return local_db.lookup(matcher, grp, buffer);
+}
+
+template <typename BufferType>
+ErrorOr<bool> lookup_by_gid(gid_t gid, struct group *grp, BufferType &buffer,
+                            const char *path) {
+  pwd::ScopedFlatFileDatabase<struct group> local_db(path);
+  const auto matcher = [gid](const struct group &entry) {
+    return entry.gr_gid == gid;
+  };
+  return local_db.lookup(matcher, grp, buffer);
+}
+
 } // namespace
 
 void TESTONLY_set_group_path(const char *path) {
@@ -207,22 +233,31 @@ ErrorOr<struct group *> read_next() {
 
 ErrorOr<bool> find_by_name(cpp::string_view name, struct group *grp,
                            cpp::span<char> buffer, const char *path) {
-  pwd::ScopedFlatFileDatabase<struct group> local_db(path ? path
-                                                          : group_file_path);
-  const auto matcher = [name](const struct group &entry) {
-    return cpp::string_view(entry.gr_name) == name;
-  };
-  return local_db.lookup(matcher, grp, buffer);
+  return lookup_by_name(name, grp, buffer, path ? path : group_file_path);
 }
 
 ErrorOr<bool> find_by_gid(gid_t gid, struct group *grp, cpp::span<char> buffer,
                           const char *path) {
-  pwd::ScopedFlatFileDatabase<struct group> local_db(path ? path
-                                                          : group_file_path);
-  const auto matcher = [gid](const struct group &entry) {
-    return entry.gr_gid == gid;
-  };
-  return local_db.lookup(matcher, grp, buffer);
+  return lookup_by_gid(gid, grp, buffer, path ? path : group_file_path);
+}
+
+ErrorOr<struct group *> find_by_name(cpp::string_view name) {
+  const auto res =
+      lookup_by_name(name, &grp_entry, line_buffer, group_file_path);
+  if (!res.has_value())
+    return Error(res.error());
+  if (!res.value())
+    return nullptr;
+  return &grp_entry;
+}
+
+ErrorOr<struct group *> find_by_gid(gid_t gid) {
+  const auto res = lookup_by_gid(gid, &grp_entry, line_buffer, group_file_path);
+  if (!res.has_value())
+    return Error(res.error());
+  if (!res.value())
+    return nullptr;
+  return &grp_entry;
 }
 
 } // namespace grp
diff --git a/libc/src/grp/grp_utils.h b/libc/src/grp/grp_utils.h
index 7c2312feaf7690..1ff2f8bd618c87 100644
--- a/libc/src/grp/grp_utils.h
+++ b/libc/src/grp/grp_utils.h
@@ -52,6 +52,9 @@ ErrorOr<bool> find_by_name(cpp::string_view name, struct group *grp,
 ErrorOr<bool> find_by_gid(gid_t gid, struct group *grp, cpp::span<char> buffer,
                           const char *path = nullptr);
 
+ErrorOr<struct group *> find_by_name(cpp::string_view name);
+ErrorOr<struct group *> find_by_gid(gid_t gid);
+
 } // namespace grp
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/src/grp/CMakeLists.txt b/libc/test/src/grp/CMakeLists.txt
index 8ea645daa4d69f..ad0bfb7df6669f 100644
--- a/libc/test/src/grp/CMakeLists.txt
+++ b/libc/test/src/grp/CMakeLists.txt
@@ -93,3 +93,60 @@ add_libc_test(
     libc.src.string.string_utils
     libc.test.UnitTest.ErrnoCheckingTest
 )
+
+add_libc_test(
+  getgrnam_test
+  SUITE
+    libc_grp_unittests
+  HDRS
+    grp_test_utils.h
+  SRCS
+    getgrnam_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.__support.ctype_utils
+    libc.src.__support.libc_errno
+    libc.src.errno.errno
+    libc.src.grp.endgrent
+    libc.src.grp.getgrent
+    libc.src.grp.getgrnam
+    libc.src.grp.grp_utils
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+)
+
+add_libc_test(
+  getgrgid_test
+  SUITE
+    libc_grp_unittests
+  HDRS
+    grp_test_utils.h
+  SRCS
+    getgrgid_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.getgrgid
+    libc.src.grp.grp_utils
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+)
diff --git a/libc/test/src/grp/getgrgid_test.cpp b/libc/test/src/grp/getgrgid_test.cpp
new file mode 100644
index 00000000000000..bf0e65c195aa61
--- /dev/null
+++ b/libc/test/src/grp/getgrgid_test.cpp
@@ -0,0 +1,178 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getgrgid.
+///
+//===----------------------------------------------------------------------===//
+
+#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/getgrgid.h"
+#include "src/grp/grp_utils.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 LlvmLibcGetgrgidTest = LlvmLibcGrpTest;
+
+TEST_F(LlvmLibcGetgrgidTest, Success) {
+  const char *content = "root:x:0:\n"
+                        "wheel:x:10:alice,bob\n"
+                        "users:x:100:carol\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_success.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrgid(10);
+  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], "alice");
+  EXPECT_STREQ(grp->gr_mem[1], "bob");
+  EXPECT_EQ(grp->gr_mem[2], nullptr);
+}
+
+TEST_F(LlvmLibcGetgrgidTest, RootGroup) {
+  const char *content = "root:x:0:\n"
+                        "wheel:x:10:alice\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_root.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrgid(0);
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(0));
+  ASSERT_NE(grp->gr_mem, nullptr);
+  EXPECT_EQ(grp->gr_mem[0], nullptr);
+}
+
+TEST_F(LlvmLibcGetgrgidTest, HighGid) {
+  const char *content = "root:x:0:\n"
+                        "nogroup:x:65534:\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_high.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrgid(65534);
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "nogroup");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(65534));
+}
+
+TEST_F(LlvmLibcGetgrgidTest, NotFound) {
+  const char *content = "root:x:0:\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_notfound.test"),
+                            content);
+
+  // POSIX specifies that errno must not be changed when an entry is not found.
+  // Pre-set errno to confirm it remains untouched across the call.
+  LIBC_NAMESPACE::libc_errno = ENOENT;
+  struct group *grp = LIBC_NAMESPACE::getgrgid(9999);
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_EQ(ENOENT);
+
+  // When errno is initially zero, it remains zero.
+  LIBC_NAMESPACE::libc_errno = 0;
+  grp = LIBC_NAMESPACE::getgrgid(9999);
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcGetgrgidTest, FileOpenFailure) {
+  const auto missing_path =
+      libc_make_test_file_path("nonexistent_dir/getgrgid_missing.test");
+  LIBC_NAMESPACE::grp::TESTONLY_set_group_path(missing_path);
+
+  ASSERT_THAT(reinterpret_cast<void *>(LIBC_NAMESPACE::getgrgid(0)),
+              Fails(ENOENT, static_cast<void *>(nullptr)));
+}
+
+TEST_F(LlvmLibcGetgrgidTest, LongRecordGrowsBuffer) {
+  // 600 four-character member names plus commas (~3,000 bytes) requires
+  // dynamic buffer growth for both the line and its member 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("getgrgid_long.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrgid(2000);
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "biggroup");
+  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);
+}
+
+TEST_F(LlvmLibcGetgrgidTest, DoesNotDisturbIteration) {
+  const char *content = "root:x:0:\n"
+                        "wheel:x:10:alice\n"
+                        "users:x:100:carol\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_iter.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  struct group *found = LIBC_NAMESPACE::getgrgid(100);
+  ASSERT_NE(found, nullptr);
+  EXPECT_STREQ(found->gr_name, "users");
+
+  // Closing the iteration stream via endgrent does not invalidate the pointer
+  // returned by getgrgid.
+  LIBC_NAMESPACE::endgrent();
+  EXPECT_STREQ(found->gr_name, "users");
+
+  // Reopening iteration starts from the beginning.
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  // A mid-iteration getgrgid lookup opens its own scoped stream so the
+  // getgrent stream position is undisturbed.
+  found = LIBC_NAMESPACE::getgrgid(100);
+  ASSERT_NE(found, nullptr);
+  EXPECT_STREQ(found->gr_name, "users");
+
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "wheel");
+
+  LIBC_NAMESPACE::endgrent();
+}
diff --git a/libc/test/src/grp/getgrnam_test.cpp b/libc/test/src/grp/getgrnam_test.cpp
new file mode 100644
index 00000000000000..049c7b1ce2b691
--- /dev/null
+++ b/libc/test/src/grp/getgrnam_test.cpp
@@ -0,0 +1,197 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getgrnam.
+///
+//===----------------------------------------------------------------------===//
+
+#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/__support/ctype_utils.h"
+#include "src/__support/libc_errno.h"
+#include "src/grp/endgrent.h"
+#include "src/grp/getgrent.h"
+#include "src/grp/getgrnam.h"
+#include "src/grp/grp_utils.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 LlvmLibcGetgrnamTest = LlvmLibcGrpTest;
+
+TEST_F(LlvmLibcGetgrnamTest, Success) {
+  const char *content = "root:x:0:\n"
+                        "wheel:x:10:alice,bob\n"
+                        "users:x:100:carol\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_success.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrnam("wheel");
+  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], "alice");
+  EXPECT_STREQ(grp->gr_mem[1], "bob");
+  EXPECT_EQ(grp->gr_mem[2], nullptr);
+}
+
+TEST_F(LlvmLibcGetgrnamTest, FirstAndLastEntries) {
+  const char *content = "first:x:100:\n"
+                        "middle:x:101:\n"
+                        "last:x:102:zed\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_boundary.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrnam("first");
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "first");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(100));
+
+  grp = LIBC_NAMESPACE::getgrnam("last");
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "last");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(102));
+  ASSERT_NE(grp->gr_mem, nullptr);
+  EXPECT_STREQ(grp->gr_mem[0], "zed");
+  EXPECT_EQ(grp->gr_mem[1], nullptr);
+}
+
+TEST_F(LlvmLibcGetgrnamTest, NotFound) {
+  const char *content = "root:x:0:\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_notfound.test"),
+                            content);
+
+  // POSIX specifies that errno must not be changed when an entry is not found.
+  // Pre-set errno to confirm it remains untouched across the call.
+  LIBC_NAMESPACE::libc_errno = ENOENT;
+  struct group *grp = LIBC_NAMESPACE::getgrnam("nonexistent");
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_EQ(ENOENT);
+
+  // When errno is initially zero, it remains zero.
+  LIBC_NAMESPACE::libc_errno = 0;
+  grp = LIBC_NAMESPACE::getgrnam("nonexistent");
+  EXPECT_EQ(grp, nullptr);
+  ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcGetgrnamTest, BlankLines) {
+  const char *content = "\nroot:x:0:\n\n\n"
+                        "wheel:x:10:alice\n\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_blank.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrnam("wheel");
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "wheel");
+  EXPECT_EQ(grp->gr_gid, static_cast<gid_t>(10));
+}
+
+TEST_F(LlvmLibcGetgrnamTest, FileOpenFailure) {
+  const auto missing_path =
+      libc_make_test_file_path("nonexistent_dir/getgrnam_missing.test");
+  LIBC_NAMESPACE::grp::TESTONLY_set_group_path(missing_path);
+
+  ASSERT_THAT(reinterpret_cast<void *>(LIBC_NAMESPACE::getgrnam("root")),
+              Fails(ENOENT, static_cast<void *>(nullptr)));
+}
+
+TEST_F(LlvmLibcGetgrnamTest, LongRecordGrowsBuffer) {
+  // 600 four-character member names plus commas (~3,000 bytes) requires
+  // dynamic buffer growth for both the line and its member 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[] = "small:x:1:\nbiggroup: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("getgrnam_long.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrnam("biggroup");
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "biggroup");
+  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);
+
+  // The buffer has grown; a short record afterwards still works.
+  grp = LIBC_NAMESPACE::getgrnam("small");
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "small");
+}
+
+TEST_F(LlvmLibcGetgrnamTest, DoesNotDisturbIteration) {
+  const char *content = "root:x:0:\n"
+                        "wheel:x:10:alice\n"
+                        "users:x:100:carol\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_iter.test"),
+                            content);
+
+  struct group *grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  struct group *found = LIBC_NAMESPACE::getgrnam("users");
+  ASSERT_NE(found, nullptr);
+  EXPECT_STREQ(found->gr_name, "users");
+
+  // Closing the iteration stream via endgrent does not invalidate the pointer
+  // returned by getgrnam.
+  LIBC_NAMESPACE::endgrent();
+  EXPECT_STREQ(found->gr_name, "users");
+
+  // Reopening iteration starts from the beginning.
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "root");
+
+  // A mid-iteration getgrnam lookup opens its own scoped stream so the
+  // getgrent stream position is undisturbed.
+  found = LIBC_NAMESPACE::getgrnam("users");
+  ASSERT_NE(found, nullptr);
+  EXPECT_STREQ(found->gr_name, "users");
+
+  grp = LIBC_NAMESPACE::getgrent();
+  ASSERT_NE(grp, nullptr);
+  EXPECT_STREQ(grp->gr_name, "wheel");
+
+  LIBC_NAMESPACE::endgrent();
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcGetgrnamTest, NullPointerCrash) {
+  ASSERT_DEATH([] { LIBC_NAMESPACE::getgrnam(nullptr); }, WITH_SIGNAL(-1));
+}
+#endif // LIBC_ADD_NULL_CHECKS



More information about the libc-commits mailing list