[libc-commits] [libc] 0e2786d - [libc] Add getgrnam_r and getgrgid_r entrypoints (#224722)

via libc-commits libc-commits at lists.llvm.org
Sat Sep 19 13:40:21 PDT 2026


Author: Jeff Bailey
Date: 2026-09-19T21:40:15+01:00
New Revision: 0e2786dcb5b28f754a1144c27e708d1c42676af9

URL: https://github.com/llvm/llvm-project/commit/0e2786dcb5b28f754a1144c27e708d1c42676af9
DIFF: https://github.com/llvm/llvm-project/commit/0e2786dcb5b28f754a1144c27e708d1c42676af9.diff

LOG: [libc] Add getgrnam_r and getgrgid_r entrypoints (#224722)

Add the reentrant group database lookup entrypoints getgrnam_r and
getgrgid_r along with grp::find_by_name and grp::find_by_gid in
grp_utils.

Each lookup opens a ScopedFlatFileDatabase<struct group> stream so that
reentrant lookups do not disturb an in-progress getgrent iteration.
Track group_file_path alongside the iteration database so scoped streams
opened by reentrant lookups honour test path overrides.

* Add getgrnam_r and getgrgid_r entrypoints
* Add find_by_name and find_by_gid lookup helpers in grp_utils
* Define getgrnam_r and getgrgid_r in include/grp.yaml
* Add hermetic unit tests for getgrnam_r and getgrgid_r

Assisted-by: Automated tooling, human reviewed.

Added: 
    libc/src/grp/getgrgid_r.cpp
    libc/src/grp/getgrgid_r.h
    libc/src/grp/getgrnam_r.cpp
    libc/src/grp/getgrnam_r.h
    libc/test/src/grp/getgrgid_r_test.cpp
    libc/test/src/grp/getgrnam_r_test.cpp

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/src/grp/grp_utils.cpp
    libc/src/grp/grp_utils.h
    libc/test/src/grp/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index fe1c2965f778b..f55ac4b16cfd2 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -48,6 +48,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
     # poll.h entrypoints

diff  --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index f4347331d0b31..1602255c6559c 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -33,6 +33,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
     # poll.h entrypoints

diff  --git a/libc/config/linux/i386/entrypoints.txt b/libc/config/linux/i386/entrypoints.txt
index f2b7dcca42c08..d5ea487b48f5e 100644
--- a/libc/config/linux/i386/entrypoints.txt
+++ b/libc/config/linux/i386/entrypoints.txt
@@ -5,6 +5,8 @@ set(TARGET_LIBC_ENTRYPOINTS
   # grp.h entrypoints
   libc.src.grp.endgrent
   libc.src.grp.getgrent
+  libc.src.grp.getgrgid_r
+  libc.src.grp.getgrnam_r
   libc.src.grp.setgrent
 
   # pwd.h entrypoints

diff  --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 51587056fb02d..921de1c649fb4 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -59,6 +59,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
     # net/if.h entrypoints

diff  --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index f21d48d6ef9e7..165484987f16d 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -59,6 +59,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # grp.h entrypoints
     libc.src.grp.endgrent
     libc.src.grp.getgrent
+    libc.src.grp.getgrgid_r
+    libc.src.grp.getgrnam_r
     libc.src.grp.setgrent
 
     # net/if.h entrypoints

diff  --git a/libc/include/grp.yaml b/libc/include/grp.yaml
index 419c1212fba3c..e1ace37cb9b29 100644
--- a/libc/include/grp.yaml
+++ b/libc/include/grp.yaml
@@ -18,6 +18,26 @@ functions:
       - posix
     return_type: struct group *
     arguments: []
+  - name: getgrgid_r
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: gid_t
+      - type: struct group *
+      - type: char *
+      - type: size_t
+      - type: struct group **
+  - name: getgrnam_r
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: const char *
+      - type: struct group *
+      - type: char *
+      - type: size_t
+      - type: struct group **
   - name: setgrent
     standards:
       - posix

diff  --git a/libc/src/grp/CMakeLists.txt b/libc/src/grp/CMakeLists.txt
index 67f3b900f55d8..1fd7b2bd3d3df 100644
--- a/libc/src/grp/CMakeLists.txt
+++ b/libc/src/grp/CMakeLists.txt
@@ -32,6 +32,40 @@ add_entrypoint_object(
     .grp_utils
 )
 
+add_entrypoint_object(
+  getgrgid_r
+  SRCS
+    getgrgid_r.cpp
+  HDRS
+    getgrgid_r.h
+  DEPENDS
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_group
+    libc.src.__support.CPP.span
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    .grp_utils
+)
+
+add_entrypoint_object(
+  getgrnam_r
+  SRCS
+    getgrnam_r.cpp
+  HDRS
+    getgrnam_r.h
+  DEPENDS
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_group
+    libc.src.__support.CPP.span
+    libc.src.__support.CPP.string_view
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    .grp_utils
+)
+
 add_entrypoint_object(
   setgrent
   SRCS
@@ -59,6 +93,7 @@ add_object_library(
     libc.hdr.types.size_t
     libc.hdr.types.struct_group
     libc.src.__support.CPP.span
+    libc.src.__support.CPP.string_view
     libc.src.__support.File.file
     libc.src.__support.File.platform_file
     libc.src.__support.ctype_utils

diff  --git a/libc/src/grp/getgrgid_r.cpp b/libc/src/grp/getgrgid_r.cpp
new file mode 100644
index 0000000000000..a2e394c3cb8ed
--- /dev/null
+++ b/libc/src/grp/getgrgid_r.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/getgrgid_r.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/null_check.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, getgrgid_r,
+                   (gid_t gid, struct group *grp, char *buffer, size_t bufsize,
+                    struct group **result)) {
+  LIBC_CRASH_ON_NULLPTR(grp);
+  LIBC_CRASH_ON_NULLPTR(buffer);
+  LIBC_CRASH_ON_NULLPTR(result);
+
+  *result = nullptr;
+
+  const auto res = grp::find_by_gid(gid, grp, cpp::span<char>(buffer, bufsize));
+  if (!res.has_value())
+    return res.error();
+
+  *result = res.value() ? grp : nullptr;
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/grp/getgrgid_r.h b/libc/src/grp/getgrgid_r.h
new file mode 100644
index 0000000000000..c7f3cc4ac5ea0
--- /dev/null
+++ b/libc/src/grp/getgrgid_r.h
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_r function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_GETGRGID_R_H
+#define LLVM_LIBC_SRC_GRP_GETGRGID_R_H
+
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize,
+               struct group **result);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_GETGRGID_R_H

diff  --git a/libc/src/grp/getgrnam_r.cpp b/libc/src/grp/getgrnam_r.cpp
new file mode 100644
index 0000000000000..78144e8c69404
--- /dev/null
+++ b/libc/src/grp/getgrnam_r.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/getgrnam_r.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/null_check.h"
+#include "src/grp/grp_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, getgrnam_r,
+                   (const char *name, struct group *grp, char *buffer,
+                    size_t bufsize, struct group **result)) {
+  LIBC_CRASH_ON_NULLPTR(name);
+  LIBC_CRASH_ON_NULLPTR(grp);
+  LIBC_CRASH_ON_NULLPTR(buffer);
+  LIBC_CRASH_ON_NULLPTR(result);
+
+  *result = nullptr;
+
+  const auto res =
+      grp::find_by_name(name, grp, cpp::span<char>(buffer, bufsize));
+  if (!res.has_value())
+    return res.error();
+
+  *result = res.value() ? grp : nullptr;
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/grp/getgrnam_r.h b/libc/src/grp/getgrnam_r.h
new file mode 100644
index 0000000000000..5066c196a592e
--- /dev/null
+++ b/libc/src/grp/getgrnam_r.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_r function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_GETGRNAM_R_H
+#define LLVM_LIBC_SRC_GRP_GETGRNAM_R_H
+
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int getgrnam_r(const char *name, struct group *grp, char *buffer,
+               size_t bufsize, struct group **result);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_GETGRNAM_R_H

diff  --git a/libc/src/grp/grp_utils.cpp b/libc/src/grp/grp_utils.cpp
index f1c4890179f12..f8cecb015461f 100644
--- a/libc/src/grp/grp_utils.cpp
+++ b/libc/src/grp/grp_utils.cpp
@@ -18,6 +18,7 @@
 #include "hdr/types/size_t.h"
 #include "hdr/types/struct_group.h"
 #include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/attributes.h"
@@ -158,6 +159,10 @@ bool parse_group_line(cpp::span<char> line, struct group *grp,
 
 namespace {
 
+// Tracked alongside the database's own copy because the reentrant lookups open
+// their own scoped database rather than sharing the iteration stream.
+const char *group_file_path = LIBC_COPT_GROUP_FILE_PATH;
+
 LIBC_CONSTINIT pwd::FlatFileDatabase<struct group>
     db(LIBC_COPT_GROUP_FILE_PATH);
 // Note: These static buffers are process-global and NOT protected by a mutex
@@ -169,20 +174,22 @@ LIBC_CONSTINIT pwd::FlatFileDatabase<struct group>
 // pointers returned prior to endgrent() remain valid until the next
 // non-reentrant call.
 LIBC_CONSTINIT pwd::DynamicBuffer line_buffer;
-struct group grp_entry;
+LIBC_CONSTINIT struct group grp_entry = {};
 
 } // namespace
 
 void TESTONLY_set_group_path(const char *path) {
   close();
   line_buffer.release();
-  db.set_path(path ? path : LIBC_COPT_GROUP_FILE_PATH);
+  group_file_path = path ? path : LIBC_COPT_GROUP_FILE_PATH;
+  db.set_path(group_file_path);
 }
 
 void TESTONLY_reset_group_path() {
   close();
   line_buffer.release();
-  db.set_path(LIBC_COPT_GROUP_FILE_PATH);
+  group_file_path = LIBC_COPT_GROUP_FILE_PATH;
+  db.set_path(group_file_path);
 }
 
 ErrorOr<void> open() { return db.setdb(); }
@@ -198,5 +205,25 @@ ErrorOr<struct group *> read_next() {
   return &grp_entry;
 }
 
+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);
+}
+
+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);
+}
+
 } // namespace grp
 } // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/grp/grp_utils.h b/libc/src/grp/grp_utils.h
index 4f5a269a855a1..7c2312feaf769 100644
--- a/libc/src/grp/grp_utils.h
+++ b/libc/src/grp/grp_utils.h
@@ -14,9 +14,11 @@
 #ifndef LLVM_LIBC_SRC_GRP_GRP_UTILS_H
 #define LLVM_LIBC_SRC_GRP_GRP_UTILS_H
 
+#include "hdr/types/gid_t.h"
 #include "hdr/types/size_t.h"
 #include "hdr/types/struct_group.h"
 #include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/pwd/flat_file_db.h"
@@ -44,6 +46,12 @@ ErrorOr<void> open();
 ErrorOr<void> close();
 ErrorOr<struct group *> read_next();
 
+ErrorOr<bool> find_by_name(cpp::string_view name, struct group *grp,
+                           cpp::span<char> buffer, const char *path = nullptr);
+
+ErrorOr<bool> find_by_gid(gid_t gid, struct group *grp, cpp::span<char> buffer,
+                          const char *path = nullptr);
+
 } // namespace grp
 } // namespace LIBC_NAMESPACE_DECL
 

diff  --git a/libc/test/src/grp/CMakeLists.txt b/libc/test/src/grp/CMakeLists.txt
index 537bb02a53db2..8ea645daa4d69 100644
--- a/libc/test/src/grp/CMakeLists.txt
+++ b/libc/test/src/grp/CMakeLists.txt
@@ -47,3 +47,49 @@ add_libc_test(
     libc.test.UnitTest.ErrnoCheckingTest
     libc.test.UnitTest.ErrnoSetterMatcher
 )
+
+add_libc_test(
+  getgrnam_r_test
+  SUITE
+    libc_grp_unittests
+  HDRS
+    grp_test_utils.h
+  SRCS
+    getgrnam_r_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.getgrnam_r
+    libc.src.grp.grp_utils
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+    libc.test.UnitTest.ErrnoCheckingTest
+)
+
+add_libc_test(
+  getgrgid_r_test
+  SUITE
+    libc_grp_unittests
+  HDRS
+    grp_test_utils.h
+  SRCS
+    getgrgid_r_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.getgrgid_r
+    libc.src.grp.grp_utils
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+    libc.test.UnitTest.ErrnoCheckingTest
+)

diff  --git a/libc/test/src/grp/getgrgid_r_test.cpp b/libc/test/src/grp/getgrgid_r_test.cpp
new file mode 100644
index 0000000000000..8d46cd7be87f1
--- /dev/null
+++ b/libc/test/src/grp/getgrgid_r_test.cpp
@@ -0,0 +1,168 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_r.
+///
+//===----------------------------------------------------------------------===//
+
+#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/getgrgid_r.h"
+#include "src/grp/grp_utils.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/grp/grp_test_utils.h"
+
+TEST_F(LlvmLibcGrpTest, GetGrgidRSuccess) {
+  const char *content = "root:x:0:root\n"
+                        "wheel:x:10:root,admin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_success.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(10, &grp, buffer, sizeof(buffer), &result), 0);
+  ASSERT_NE(result, nullptr);
+  EXPECT_STREQ(grp.gr_name, "wheel");
+  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_EQ(grp.gr_mem[2], nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrgidRRootGidZero) {
+  const char *content = "root:x:0:root\n"
+                        "wheel:x:10:admin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_zero.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(0, &grp, buffer, sizeof(buffer), &result), 0);
+  ASSERT_NE(result, nullptr);
+  EXPECT_STREQ(grp.gr_name, "root");
+  EXPECT_EQ(grp.gr_gid, static_cast<gid_t>(0));
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrgidREmptyMemberList) {
+  const char *content = "nogroup:x:65534:\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_empty.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(65534, &grp, buffer, sizeof(buffer), &result),
+      0);
+  ASSERT_NE(result, nullptr);
+  EXPECT_STREQ(grp.gr_name, "nogroup");
+  ASSERT_NE(grp.gr_mem, nullptr);
+  EXPECT_EQ(grp.gr_mem[0], nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrgidRNotFound) {
+  const char *content = "root:x:0:root\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_absent.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = &grp;
+  char buffer[256];
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(4242, &grp, buffer, sizeof(buffer), &result),
+      0);
+  ASSERT_EQ(result, nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrgidRBufferTooSmall) {
+  const char *content = "wheel:x:10:root,admin,user1\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_small.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = &grp;
+  char buffer[8];
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(10, &grp, buffer, sizeof(buffer), &result),
+      ERANGE);
+  ASSERT_EQ(result, nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrgidRBlankLines) {
+  const char *content = "\nroot:x:0:root\n\n\nwheel:x:10:admin\n\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_blank.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(10, &grp, buffer, sizeof(buffer), &result), 0);
+  ASSERT_NE(result, nullptr);
+  EXPECT_STREQ(grp.gr_name, "wheel");
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrgidRDoesNotDisturbIteration) {
+  const char *content = "root:x:0:root\n"
+                        "bin:x:1:bin\n"
+                        "wheel:x:10:admin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrgid_r_iter.test"),
+                            content);
+
+  const auto first = LIBC_NAMESPACE::grp::read_next();
+  ASSERT_TRUE(first.has_value());
+  ASSERT_NE(first.value(), nullptr);
+  EXPECT_STREQ(first.value()->gr_name, "root");
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getgrgid_r(10, &grp, buffer, sizeof(buffer), &result), 0);
+  ASSERT_NE(result, nullptr);
+
+  const auto second = LIBC_NAMESPACE::grp::read_next();
+  ASSERT_TRUE(second.has_value());
+  ASSERT_NE(second.value(), nullptr);
+  EXPECT_STREQ(second.value()->gr_name, "bin");
+
+  LIBC_NAMESPACE::grp::close();
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcGrpTest, NullPointerCrash) {
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[64];
+  ASSERT_DEATH(
+      [&] {
+        LIBC_NAMESPACE::getgrgid_r(0, nullptr, buffer, sizeof(buffer), &result);
+      },
+      WITH_SIGNAL(-1));
+  ASSERT_DEATH(
+      [&] {
+        LIBC_NAMESPACE::getgrgid_r(0, &grp, buffer, sizeof(buffer), nullptr);
+      },
+      WITH_SIGNAL(-1));
+}
+#endif // LIBC_ADD_NULL_CHECKS

diff  --git a/libc/test/src/grp/getgrnam_r_test.cpp b/libc/test/src/grp/getgrnam_r_test.cpp
new file mode 100644
index 0000000000000..ad336f50e7a7b
--- /dev/null
+++ b/libc/test/src/grp/getgrnam_r_test.cpp
@@ -0,0 +1,187 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_r.
+///
+//===----------------------------------------------------------------------===//
+
+#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/getgrnam_r.h"
+#include "src/grp/grp_utils.h"
+#include "test/UnitTest/Test.h"
+#include "test/src/grp/grp_test_utils.h"
+
+TEST_F(LlvmLibcGrpTest, GetGrnamRSuccess) {
+  const char *content = "root:x:0:root\n"
+                        "wheel:x:10:root,admin,user1\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_success.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+
+  ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
+                                       &result),
+            0);
+  ASSERT_EQ(result, &grp);
+  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);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrnamRResultLivesInCallerBuffer) {
+  const char *content = "wheel:x:10:root,admin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_buf.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+
+  ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
+                                       &result),
+            0);
+  ASSERT_NE(result, nullptr);
+
+  // POSIX requires the strings and the member array to be stored in the
+  // caller's buffer, not in any library-owned storage.
+  char *begin = buffer;
+  char *end = buffer + sizeof(buffer);
+  EXPECT_GE(grp.gr_name, begin);
+  EXPECT_LT(grp.gr_name, end);
+  EXPECT_GE(grp.gr_passwd, begin);
+  EXPECT_LT(grp.gr_passwd, end);
+  EXPECT_GE(reinterpret_cast<char *>(grp.gr_mem), begin);
+  EXPECT_LT(reinterpret_cast<char *>(grp.gr_mem), end);
+  EXPECT_GE(grp.gr_mem[0], begin);
+  EXPECT_LT(grp.gr_mem[0], end);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrnamRNotFound) {
+  const char *content = "root:x:0:root\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_absent.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = &grp;
+  char buffer[256];
+
+  ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("nosuchgroup", &grp, buffer,
+                                       sizeof(buffer), &result),
+            0);
+  // Not found is not an error: zero is returned with a null result.
+  ASSERT_EQ(result, nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrnamRBufferTooSmallForRecord) {
+  const char *content = "wheel:x:10:root,admin,user1\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_small.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = &grp;
+  char buffer[8];
+
+  ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
+                                       &result),
+            ERANGE);
+  ASSERT_EQ(result, nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrnamRBufferTooSmallForMemberArray) {
+  // The record fits, but the member pointer array carved out of the space
+  // after it does not. This must be reported as ERANGE rather than silently
+  // truncating the member list.
+  const char *content = "wheel:x:10:a,b,c,d,e,f,g,h\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_mem.test"),
+                            content);
+
+  struct group grp;
+  struct group *result = &grp;
+  // "wheel:x:10:a,b,c,d,e,f,g,h" is 26 bytes excluding the newline. Adding 1
+  // for the null terminator leaves 5 bytes in a 32-byte buffer, which is too
+  // small for the 9 char * pointers needed by 8 members plus the terminating
+  // nullptr.
+  constexpr size_t RECORD_LEN = 26;
+  char buffer[RECORD_LEN + 6];
+
+  ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
+                                       &result),
+            ERANGE);
+  ASSERT_EQ(result, nullptr);
+}
+
+TEST_F(LlvmLibcGrpTest, GetGrnamRDoesNotDisturbIteration) {
+  const char *content = "root:x:0:root\n"
+                        "bin:x:1:bin\n"
+                        "wheel:x:10:admin\n";
+  ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_iter.test"),
+                            content);
+
+  const auto first = LIBC_NAMESPACE::grp::read_next();
+  ASSERT_TRUE(first.has_value());
+  ASSERT_NE(first.value(), nullptr);
+  EXPECT_STREQ(first.value()->gr_name, "root");
+
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[256];
+  ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
+                                       &result),
+            0);
+  ASSERT_NE(result, nullptr);
+
+  // The reentrant lookup uses its own stream, so iteration continues from
+  // where it left off.
+  const auto second = LIBC_NAMESPACE::grp::read_next();
+  ASSERT_TRUE(second.has_value());
+  ASSERT_NE(second.value(), nullptr);
+  EXPECT_STREQ(second.value()->gr_name, "bin");
+
+  LIBC_NAMESPACE::grp::close();
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcGrpTest, NullPointerCrash) {
+  struct group grp;
+  struct group *result = nullptr;
+  char buffer[64];
+  ASSERT_DEATH(
+      [] {
+        struct group local_grp;
+        struct group *local_result = nullptr;
+        char local_buf[64];
+        LIBC_NAMESPACE::getgrnam_r(nullptr, &local_grp, local_buf,
+                                   sizeof(local_buf), &local_result);
+      },
+      WITH_SIGNAL(-1));
+  ASSERT_DEATH(
+      [&] {
+        LIBC_NAMESPACE::getgrnam_r("root", nullptr, buffer, sizeof(buffer),
+                                   &result);
+      },
+      WITH_SIGNAL(-1));
+  ASSERT_DEATH(
+      [&] {
+        LIBC_NAMESPACE::getgrnam_r("root", &grp, buffer, sizeof(buffer),
+                                   nullptr);
+      },
+      WITH_SIGNAL(-1));
+}
+#endif // LIBC_ADD_NULL_CHECKS


        


More information about the libc-commits mailing list