[libc-commits] [libc] [libc] Add struct group header and group line parser (PR #224208)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Thu Sep 17 00:13:24 PDT 2026
https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/224208
Introduce the POSIX struct group header and grp_utils parser module for colon-delimited group records.
Unlike struct passwd, struct group carries a null-terminated char** member pointer array (gr_mem). parse_line<struct group> handles placing the gr_mem pointer array into the aligned scratch span directly:
* Fixed-buffer callers (such as reentrant lookups) report ERANGE if the provided scratch span cannot hold the member pointers.
* Growable DynamicBuffer callers grow the buffer when parse_line returns ERANGE and retry before parsing in place.
* Add struct_group header in hdr/types/struct_group.h
* Expose libc.include.grp in Linux arm and i386 headers.txt
* Add grp_utils parser and database helpers in src/grp/
* Add unit tests in test/src/grp/grp_utils_test.cpp
Assisted-by: Automated tooling, human reviewed.
>From 4b95fc10fdb4c551a9d720dee619add41e4d8402 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Tue, 15 Sep 2026 18:05:33 +0100
Subject: [PATCH] [libc] Add struct group header and group line parser
Introduce the POSIX struct group header and grp_utils parser module for
colon-delimited group records.
Unlike struct passwd, struct group carries a null-terminated char**
member pointer array (gr_mem). parse_line<struct group> handles placing
the gr_mem pointer array into the aligned scratch span directly:
* Fixed-buffer callers (such as reentrant lookups) report ERANGE if the
provided scratch span cannot hold the member pointers.
* Growable DynamicBuffer callers grow the buffer when parse_line returns
ERANGE and retry before parsing in place.
* Add struct_group header in hdr/types/struct_group.h
* Expose libc.include.grp in Linux arm and i386 headers.txt
* Add grp_utils parser and database helpers in src/grp/
* Add unit tests in test/src/grp/grp_utils_test.cpp
Assisted-by: Automated tooling, human reviewed.
---
libc/config/linux/arm/headers.txt | 1 +
libc/config/linux/i386/headers.txt | 3 +-
libc/hdr/types/CMakeLists.txt | 8 ++
libc/hdr/types/struct_group.h | 27 ++++
libc/src/CMakeLists.txt | 1 +
libc/src/grp/CMakeLists.txt | 29 ++++
libc/src/grp/grp_utils.cpp | 199 +++++++++++++++++++++++++++
libc/src/grp/grp_utils.h | 50 +++++++
libc/test/src/CMakeLists.txt | 1 +
libc/test/src/grp/CMakeLists.txt | 21 +++
libc/test/src/grp/grp_utils_test.cpp | 191 +++++++++++++++++++++++++
11 files changed, 530 insertions(+), 1 deletion(-)
create mode 100644 libc/hdr/types/struct_group.h
create mode 100644 libc/src/grp/CMakeLists.txt
create mode 100644 libc/src/grp/grp_utils.cpp
create mode 100644 libc/src/grp/grp_utils.h
create mode 100644 libc/test/src/grp/CMakeLists.txt
create mode 100644 libc/test/src/grp/grp_utils_test.cpp
diff --git a/libc/config/linux/arm/headers.txt b/libc/config/linux/arm/headers.txt
index f8426f32a3bfc..af0631843a157 100644
--- a/libc/config/linux/arm/headers.txt
+++ b/libc/config/linux/arm/headers.txt
@@ -7,6 +7,7 @@ set(TARGET_PUBLIC_HEADERS
libc.include.errno
libc.include.fenv
libc.include.float
+ libc.include.grp
libc.include.inttypes
libc.include.langinfo
libc.include.libgen
diff --git a/libc/config/linux/i386/headers.txt b/libc/config/linux/i386/headers.txt
index 1235db6e0b119..21c792b046e19 100644
--- a/libc/config/linux/i386/headers.txt
+++ b/libc/config/linux/i386/headers.txt
@@ -1,7 +1,8 @@
set(TARGET_PUBLIC_HEADERS
libc.include.alloca
libc.include.assert
- libc.include.pwd
libc.include.cpio
+ libc.include.grp
libc.include.langinfo
+ libc.include.pwd
)
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 36819d531d164..6949e73a78daa 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -185,6 +185,14 @@ add_proxy_header_library(
libc.include.llvm-libc-types.struct_passwd
)
+add_proxy_header_library(
+ struct_group
+ HDRS
+ struct_group.h
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.struct_group
+)
+
add_proxy_header_library(
rsize_t
HDRS
diff --git a/libc/hdr/types/struct_group.h b/libc/hdr/types/struct_group.h
new file mode 100644
index 0000000000000..ce4d382e8f19d
--- /dev/null
+++ b/libc/hdr/types/struct_group.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
+/// Proxy for struct group.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_GROUP_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_GROUP_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_group.h"
+
+#else
+
+#include <grp.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_GROUP_H
diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt
index ad2716cdc0724..0c2d776e5faae 100644
--- a/libc/src/CMakeLists.txt
+++ b/libc/src/CMakeLists.txt
@@ -31,6 +31,7 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
add_subdirectory(dirent)
add_subdirectory(err)
add_subdirectory(fcntl)
+ add_subdirectory(grp)
add_subdirectory(poll)
add_subdirectory(pthread)
add_subdirectory(pwd)
diff --git a/libc/src/grp/CMakeLists.txt b/libc/src/grp/CMakeLists.txt
new file mode 100644
index 0000000000000..9e460e376a14f
--- /dev/null
+++ b/libc/src/grp/CMakeLists.txt
@@ -0,0 +1,29 @@
+if(NOT TARGET libc.src.__support.File.file OR
+ NOT TARGET libc.src.__support.File.platform_file)
+ return()
+endif()
+
+add_object_library(
+ grp_utils
+ HDRS
+ grp_utils.h
+ SRCS
+ grp_utils.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.stdint_proxy
+ libc.hdr.types.gid_t
+ libc.hdr.types.size_t
+ libc.hdr.types.struct_group
+ libc.src.__support.CPP.span
+ libc.src.__support.File.file
+ libc.src.__support.File.platform_file
+ libc.src.__support.ctype_utils
+ libc.src.__support.error_or
+ libc.src.__support.macros.attributes
+ libc.src.__support.macros.config
+ libc.src.__support.pwd.dynamic_buffer
+ libc.src.__support.pwd.field_tokenizer
+ libc.src.__support.pwd.flat_file_db
+ libc.src.__support.str_to_integer
+)
diff --git a/libc/src/grp/grp_utils.cpp b/libc/src/grp/grp_utils.cpp
new file mode 100644
index 0000000000000..6439070e30055
--- /dev/null
+++ b/libc/src/grp/grp_utils.cpp
@@ -0,0 +1,199 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 helper functions and parser for grp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/grp/grp_utils.h"
+#include "hdr/errno_macros.h"
+#include "hdr/stdint_proxy.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/ctype_utils.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/pwd/dynamic_buffer.h"
+#include "src/__support/pwd/field_tokenizer.h"
+#include "src/__support/pwd/flat_file_db.h"
+#include "src/__support/str_to_integer.h"
+
+#ifndef LIBC_COPT_GROUP_FILE_PATH
+#define LIBC_COPT_GROUP_FILE_PATH "/etc/group"
+#endif
+
+namespace LIBC_NAMESPACE_DECL {
+namespace {
+
+// TODO: Replace with cpp::count when available in
+// src/__support/CPP/algorithm.h.
+size_t count_group_members(cpp::span<const char> line) {
+ size_t max_members = 1;
+ for (char c : line) {
+ if (c == ',')
+ ++max_members;
+ }
+ return max_members;
+}
+
+// Parse fixed fields (name, passwd, gid).
+bool parse_group_fields(cpp::span<char> line, struct group *grp,
+ cpp::span<char> *members_out) {
+ if (line.empty() || !grp || !members_out)
+ return false;
+
+ pwd::FieldTokenizer tokenizer(line, ':');
+
+ auto name = tokenizer.next_field();
+ if (!name || name->empty() || name->front() == '\0')
+ return false;
+ grp->gr_name = name->data();
+
+ auto passwd = tokenizer.next_field();
+ if (!passwd)
+ return false;
+ grp->gr_passwd = passwd->data();
+
+ auto gid_str = tokenizer.next_field();
+ if (!gid_str || gid_str->empty() || !internal::isdigit(gid_str->front()))
+ return false;
+ auto gid_res = internal::strtointeger<gid_t>(gid_str->data(), 10);
+ if (gid_res.has_error() || gid_res.parsed_len <= 0 ||
+ static_cast<size_t>(gid_res.parsed_len) + 1 != gid_str->size() ||
+ (*gid_str)[gid_res.parsed_len] != '\0')
+ return false;
+ grp->gr_gid = gid_res.value;
+
+ auto members_field = tokenizer.next_field();
+ if (!members_field)
+ return false;
+
+ // Trailing delimiters or fields are invalid.
+ if (tokenizer.next_field())
+ return false;
+
+ *members_out = *members_field;
+ return true;
+}
+
+} // namespace
+
+namespace pwd {
+
+template <>
+ErrorOr<void> parse_line<struct group>(cpp::span<char> line,
+ cpp::span<char> scratch,
+ struct group *grp) {
+ if (!grp || line.empty() || line.back() != '\0')
+ return Error(EINVAL);
+
+ // Line excluding terminating null byte.
+ cpp::span<char> text = line.first(line.size() - 1);
+ for (char c : text) {
+ if (c == '\0')
+ return Error(EINVAL);
+ }
+
+ size_t max_members = count_group_members(text);
+ uintptr_t tail = reinterpret_cast<uintptr_t>(scratch.data());
+ size_t pad = (alignof(char *) - tail % alignof(char *)) % alignof(char *);
+ if (scratch.size() < pad)
+ return Error(ERANGE);
+ size_t remaining_bytes = scratch.size() - pad;
+ if (remaining_bytes / sizeof(char *) < max_members + 1)
+ return Error(ERANGE);
+
+ cpp::span<char *> mem_ptrs(
+ reinterpret_cast<char **>(scratch.subspan(pad).data()),
+ remaining_bytes / sizeof(char *));
+ if (!grp::parse_group_line(line, grp, mem_ptrs))
+ return Error(EINVAL);
+ return {};
+}
+
+} // namespace pwd
+
+namespace grp {
+
+bool parse_group_line(cpp::span<char> line, struct group *grp,
+ cpp::span<char *> mem_ptrs) {
+ if (mem_ptrs.empty())
+ return false;
+
+ cpp::span<char> members_field;
+ if (!parse_group_fields(line, grp, &members_field))
+ return false;
+
+ size_t member_count = 0;
+ if (!members_field.empty() && members_field.front() != '\0') {
+ pwd::FieldTokenizer member_tokenizer(members_field, ',');
+ while (auto member = member_tokenizer.next_field()) {
+ if (member->empty() || member->front() == '\0')
+ continue;
+ if (member_count + 1 >= mem_ptrs.size())
+ return false;
+ mem_ptrs[member_count++] = member->data();
+ }
+ }
+
+ if (member_count >= mem_ptrs.size())
+ return false;
+ mem_ptrs[member_count] = nullptr;
+ grp->gr_mem = mem_ptrs.data();
+
+ return true;
+}
+
+namespace {
+
+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
+// 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
+// pointers returned prior to endgrent() remain valid until the next
+// non-reentrant call.
+LIBC_CONSTINIT pwd::DynamicBuffer line_buffer;
+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);
+}
+
+void TESTONLY_reset_group_path() {
+ close();
+ line_buffer.release();
+ db.set_path(LIBC_COPT_GROUP_FILE_PATH);
+}
+
+ErrorOr<void> open() { return db.setdb(); }
+
+ErrorOr<void> close() { return db.enddb(); }
+
+ErrorOr<struct group *> read_next() {
+ auto res = db.getnext(&grp_entry, line_buffer);
+ if (!res.has_value())
+ return Error(res.error());
+ if (!res.value())
+ return nullptr;
+ return &grp_entry;
+}
+
+} // namespace grp
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/grp/grp_utils.h b/libc/src/grp/grp_utils.h
new file mode 100644
index 0000000000000..4f5a269a855a1
--- /dev/null
+++ b/libc/src/grp/grp_utils.h
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Declarations of helper functions and parser for grp.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_GRP_GRP_UTILS_H
+#define LLVM_LIBC_SRC_GRP_GRP_UTILS_H
+
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_group.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/pwd/flat_file_db.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace pwd {
+
+template <>
+ErrorOr<void> parse_line<struct group>(cpp::span<char> line,
+ cpp::span<char> scratch,
+ struct group *grp);
+
+} // namespace pwd
+
+namespace grp {
+
+// Parse a colon-separated group line into a struct group.
+bool parse_group_line(cpp::span<char> line, struct group *grp,
+ cpp::span<char *> mem_ptrs);
+
+void TESTONLY_set_group_path(const char *path);
+void TESTONLY_reset_group_path();
+
+ErrorOr<void> open();
+ErrorOr<void> close();
+ErrorOr<struct group *> read_next();
+
+} // namespace grp
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_GRP_GRP_UTILS_H
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index f8758930817ae..463485768dcde 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -100,6 +100,7 @@ add_subdirectory(inttypes)
if(${LIBC_TARGET_OS} STREQUAL "linux")
add_subdirectory(err)
add_subdirectory(fcntl)
+ add_subdirectory(grp)
add_subdirectory(poll)
add_subdirectory(sched)
add_subdirectory(sys)
diff --git a/libc/test/src/grp/CMakeLists.txt b/libc/test/src/grp/CMakeLists.txt
new file mode 100644
index 0000000000000..dbc1ac765455b
--- /dev/null
+++ b/libc/test/src/grp/CMakeLists.txt
@@ -0,0 +1,21 @@
+add_custom_target(libc_grp_unittests)
+
+if(NOT TARGET libc.src.grp.grp_utils)
+ return()
+endif()
+
+add_libc_test(
+ grp_utils_test
+ SUITE
+ libc_grp_unittests
+ SRCS
+ grp_utils_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.CPP.span
+ libc.src.__support.pwd.flat_file_db
+ libc.src.grp.grp_utils
+)
diff --git a/libc/test/src/grp/grp_utils_test.cpp b/libc/test/src/grp/grp_utils_test.cpp
new file mode 100644
index 0000000000000..d07ce965b411c
--- /dev/null
+++ b/libc/test/src/grp/grp_utils_test.cpp
@@ -0,0 +1,191 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unittests for parse_group_line.
+///
+//===----------------------------------------------------------------------===//
+
+#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/CPP/span.h"
+#include "src/__support/pwd/flat_file_db.h"
+#include "src/grp/grp_utils.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_Success) {
+ char line[] = "wheel:x:10:root,admin,user1";
+ char *mem_ptrs[8];
+ struct group grp;
+ bool ok = LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs);
+ ASSERT_TRUE(ok);
+ 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(LlvmLibcGrpUtilsTest, ParseGroupLine_EmptyMembers) {
+ char line[] = "nogroup:x:65534:";
+ char *mem_ptrs[4];
+ struct group grp;
+ bool ok = LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs);
+ ASSERT_TRUE(ok);
+ EXPECT_STREQ(grp.gr_name, "nogroup");
+ EXPECT_STREQ(grp.gr_passwd, "x");
+ EXPECT_EQ(grp.gr_gid, static_cast<gid_t>(65534));
+ ASSERT_NE(grp.gr_mem, nullptr);
+ EXPECT_EQ(grp.gr_mem[0], nullptr);
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_LeadingTrailingAndConsecutiveCommas) {
+ char line[] = "test:x:100:,user1,,user2,";
+ char *mem_ptrs[8];
+ struct group grp;
+ bool ok = LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs);
+ ASSERT_TRUE(ok);
+ EXPECT_STREQ(grp.gr_name, "test");
+ EXPECT_STREQ(grp.gr_passwd, "x");
+ EXPECT_EQ(grp.gr_gid, static_cast<gid_t>(100));
+ ASSERT_NE(grp.gr_mem, nullptr);
+ EXPECT_STREQ(grp.gr_mem[0], "user1");
+ EXPECT_STREQ(grp.gr_mem[1], "user2");
+ EXPECT_EQ(grp.gr_mem[2], nullptr);
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_SingleMember) {
+ char line[] = "bin:x:1:bin";
+ char *mem_ptrs[4];
+ struct group grp;
+ bool ok = LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs);
+ ASSERT_TRUE(ok);
+ 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_EQ(grp.gr_mem[1], nullptr);
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_InvalidNumeric) {
+ char *mem_ptrs[4];
+ struct group grp;
+
+ char line1[] = "root:x:abc:root";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line1, &grp, mem_ptrs));
+
+ char line2[] = "root:x:-1:root";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line2, &grp, mem_ptrs));
+
+ char line3[] = "root:x::root";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line3, &grp, mem_ptrs));
+
+ char line4[] = "root:x:999999999999999999999999999999:root";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line4, &grp, mem_ptrs));
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_MissingFields) {
+ char *mem_ptrs[4];
+ struct group grp;
+
+ char line1[] = "root:x";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line1, &grp, mem_ptrs));
+
+ char line2[] = "root:x:0";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line2, &grp, mem_ptrs));
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_EmptyGroupName) {
+ char *mem_ptrs[4];
+ struct group grp;
+ char line[] = ":x:0:root";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs));
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_TrailingGarbage) {
+ char *mem_ptrs[4];
+ struct group grp;
+ char line[] = "root:x:0:root:extra";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs));
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_NullInput) {
+ char *mem_ptrs[4];
+ struct group grp;
+ LIBC_NAMESPACE::cpp::span<char> empty;
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(empty, &grp, mem_ptrs));
+
+ char line[] = "root:x:0:root";
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line, nullptr, mem_ptrs));
+
+ LIBC_NAMESPACE::cpp::span<char *> empty_mem;
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line, &grp, empty_mem));
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseGroupLine_MemberBufferTooSmall) {
+ char line[] = "wheel:x:10:root,admin";
+ char *mem_ptrs[2]; // Can only hold 1 member + 1 nullptr
+ struct group grp;
+ EXPECT_FALSE(LIBC_NAMESPACE::grp::parse_group_line(line, &grp, mem_ptrs));
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseLine_FixedBufferErangeWhenNoScratchSpace) {
+ // The line fits in buffer, but there is no space left for gr_mem pointers.
+ char buffer[] = "wheel:x:10:root,admin";
+ struct group grp;
+ auto res = LIBC_NAMESPACE::pwd::parse_line<struct group>(
+ LIBC_NAMESPACE::cpp::span<char>(buffer, sizeof(buffer)), {}, &grp);
+ ASSERT_FALSE(res.has_value());
+ EXPECT_EQ(res.error(), ERANGE);
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseLine_EmbeddedNullByteRejected) {
+ constexpr const char RAW[] = "wheel:x:10:root\0admin";
+ char buffer[128];
+ for (size_t i = 0; i < sizeof(RAW); ++i)
+ buffer[i] = RAW[i];
+ struct group grp;
+ auto res = LIBC_NAMESPACE::pwd::parse_line<struct group>(
+ LIBC_NAMESPACE::cpp::span<char>(buffer, sizeof(RAW)),
+ LIBC_NAMESPACE::cpp::span<char>(buffer + sizeof(RAW),
+ sizeof(buffer) - sizeof(RAW)),
+ &grp);
+ ASSERT_FALSE(res.has_value());
+ EXPECT_EQ(res.error(), EINVAL);
+}
+
+TEST(LlvmLibcGrpUtilsTest, ParseLine_SuccessWithTailForMemberPointers) {
+ constexpr const char *LINE = "wheel:x:10:root,admin,user1,user2";
+ constexpr size_t LEN = 33;
+ char buffer[128];
+ for (size_t i = 0; i <= LEN; ++i)
+ buffer[i] = LINE[i];
+
+ struct group grp;
+ auto res = LIBC_NAMESPACE::pwd::parse_line<struct group>(
+ LIBC_NAMESPACE::cpp::span<char>(buffer, LEN + 1),
+ LIBC_NAMESPACE::cpp::span<char>(buffer + LEN + 1,
+ sizeof(buffer) - (LEN + 1)),
+ &grp);
+ ASSERT_TRUE(res.has_value());
+ 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_STREQ(grp.gr_mem[3], "user2");
+ EXPECT_EQ(grp.gr_mem[4], nullptr);
+}
More information about the libc-commits
mailing list