[libc-commits] [libc] [libc] Add getpwent, setpwent, and endpwent entrypoints (PR #213076)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Thu Jul 30 10:02:53 PDT 2026
https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/213076
Added getpwent, setpwent, and endpwent functions using the internal pwd_utils line parser.
* Implemented getpwent.cpp, setpwent.cpp, and endpwent.cpp
* Added internal file management helpers set_passwd_path, setpwent_impl, and endpwent_impl returning ErrorOr<int> in getpwent.h
* Registered entrypoints in config/linux/x86_64/entrypoints.txt
* Added unit tests in libc/test/src/pwd/getpwent_test.cpp
Assisted-by: Automated tooling, human reviewed.
>From 0bd302dcdafab81bb51db689a015aa0bcd104310 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Tue, 28 Jul 2026 09:10:24 +0100
Subject: [PATCH] [libc] Add getpwent, setpwent, and endpwent entrypoints
Added getpwent, setpwent, and endpwent functions using the internal
pwd_utils line parser.
* Implemented getpwent.cpp, setpwent.cpp, and endpwent.cpp
* Added internal file management helpers set_passwd_path, setpwent_impl,
and endpwent_impl returning ErrorOr<int> in getpwent.h
* Registered entrypoints in config/linux/x86_64/entrypoints.txt
* Added unit tests in libc/test/src/pwd/getpwent_test.cpp
Assisted-by: Automated tooling, human reviewed.
---
libc/config/linux/x86_64/entrypoints.txt | 5 +
libc/src/pwd/CMakeLists.txt | 43 ++++++
libc/src/pwd/endpwent.cpp | 29 ++++
libc/src/pwd/endpwent.h | 26 ++++
libc/src/pwd/getpwent.cpp | 170 +++++++++++++++++++++++
libc/src/pwd/getpwent.h | 40 ++++++
libc/src/pwd/setpwent.cpp | 29 ++++
libc/src/pwd/setpwent.h | 26 ++++
libc/test/src/pwd/CMakeLists.txt | 18 +++
libc/test/src/pwd/getpwent_test.cpp | 152 ++++++++++++++++++++
10 files changed, 538 insertions(+)
create mode 100644 libc/src/pwd/endpwent.cpp
create mode 100644 libc/src/pwd/endpwent.h
create mode 100644 libc/src/pwd/getpwent.cpp
create mode 100644 libc/src/pwd/getpwent.h
create mode 100644 libc/src/pwd/setpwent.cpp
create mode 100644 libc/src/pwd/setpwent.h
create mode 100644 libc/test/src/pwd/getpwent_test.cpp
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 619fc9b85ae48..10db4cc9378e5 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -64,6 +64,11 @@ set(TARGET_LIBC_ENTRYPOINTS
# poll.h entrypoints
libc.src.poll.poll
+ # pwd.h entrypoints
+ libc.src.pwd.endpwent
+ libc.src.pwd.getpwent
+ libc.src.pwd.setpwent
+
# sched.h entrypoints
libc.src.sched.getcpu
libc.src.sched.sched_get_priority_max
diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
index f8498b27eaa54..96d58c68359c0 100644
--- a/libc/src/pwd/CMakeLists.txt
+++ b/libc/src/pwd/CMakeLists.txt
@@ -1,3 +1,46 @@
+add_entrypoint_object(
+ getpwent
+ SRCS
+ getpwent.cpp
+ HDRS
+ getpwent.h
+ DEPENDS
+ libc.hdr.types.struct_passwd
+ libc.src.errno.errno
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ libc.src.__support.File.file
+ libc.src.__support.File.platform_file
+ libc.hdr.stdio_macros
+ .pwd_utils
+)
+
+add_entrypoint_object(
+ setpwent
+ SRCS
+ setpwent.cpp
+ HDRS
+ setpwent.h
+ DEPENDS
+ libc.src.errno.errno
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ .getpwent
+)
+
+add_entrypoint_object(
+ endpwent
+ SRCS
+ endpwent.cpp
+ HDRS
+ endpwent.h
+ DEPENDS
+ libc.src.errno.errno
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ .getpwent
+)
+
add_object_library(
pwd_utils
HDRS
diff --git a/libc/src/pwd/endpwent.cpp b/libc/src/pwd/endpwent.cpp
new file mode 100644
index 0000000000000..c0e920e475ad2
--- /dev/null
+++ b/libc/src/pwd/endpwent.cpp
@@ -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
+/// Implementation of endpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/endpwent.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/pwd/getpwent.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, endpwent, ()) {
+ // endpwent_impl closes the password file. If an error occurs,
+ // it returns an Error with an errno value which is set here.
+ auto res = endpwent_impl();
+ if (!res.has_value())
+ libc_errno = res.error();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/endpwent.h b/libc/src/pwd/endpwent.h
new file mode 100644
index 0000000000000..e23ef3ea6dadf
--- /dev/null
+++ b/libc/src/pwd/endpwent.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 endpwent function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_ENDPWENT_H
+#define LLVM_LIBC_SRC_PWD_ENDPWENT_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Closes the password database stream and releases associated resources.
+void endpwent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_ENDPWENT_H
diff --git a/libc/src/pwd/getpwent.cpp b/libc/src/pwd/getpwent.cpp
new file mode 100644
index 0000000000000..6a69f92b71694
--- /dev/null
+++ b/libc/src/pwd/getpwent.cpp
@@ -0,0 +1,170 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/getpwent.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/File/file.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/pwd/pwd_utils.h"
+
+#include "hdr/stdio_macros.h"
+
+#ifndef LIBC_COPT_PWD_FILE_PATH
+#define LIBC_COPT_PWD_FILE_PATH "/etc/passwd"
+#endif
+
+namespace LIBC_NAMESPACE_DECL {
+
+static File *pwd_file = nullptr;
+static const char *pwd_file_path = LIBC_COPT_PWD_FILE_PATH;
+// Note: These static buffers are process-global and NOT protected by a mutex
+// at this stage. POSIX getpwent is non-reentrant.
+static char line_buffer[1024];
+static struct passwd pwd_entry;
+
+namespace internal {
+void set_passwd_path(const char *path) {
+ if (!path)
+ return;
+ if (pwd_file) {
+ pwd_file->close();
+ pwd_file = nullptr;
+ }
+ pwd_file_path = path;
+}
+} // namespace internal
+
+ErrorOr<int> setpwent_impl() {
+ if (!pwd_file) {
+ auto result = openfile(pwd_file_path, "r");
+ if (!result.has_value())
+ return Error(result.error());
+ pwd_file = result.value();
+ } else {
+ auto result = pwd_file->seek(0, SEEK_SET);
+ if (!result.has_value())
+ return Error(result.error());
+ }
+ return 0;
+}
+
+ErrorOr<int> endpwent_impl() {
+ if (pwd_file) {
+ int result = pwd_file->close();
+ pwd_file = nullptr;
+ if (result != 0)
+ return Error(result);
+ }
+ return 0;
+}
+
+struct ReadLineResult {
+ size_t bytes_read;
+ bool truncated;
+};
+
+// Reads a line from the given file into buf.
+static ErrorOr<ReadLineResult> read_line(File *f, cpp::span<char> buf) {
+ if (!f || buf.empty())
+ return Error(EINVAL);
+
+ f->lock();
+ size_t bytes_read = 0;
+ FileIOResult result(0);
+ bool truncated = false;
+
+ for (char &ch : buf.first(buf.size() - 1)) {
+ result = f->read_unlocked(&ch, 1);
+ if (result.has_error()) {
+ f->unlock();
+ return Error(result.error);
+ }
+ if (result.value != 1)
+ break;
+ ++bytes_read;
+ if (ch == '\n')
+ break;
+ }
+
+ if (result.value == 1 && bytes_read > 0 && buf[bytes_read - 1] != '\n') {
+ truncated = true;
+ char c = '\0';
+ while (true) {
+ result = f->read_unlocked(&c, 1);
+ if (result.has_error()) {
+ f->unlock();
+ return Error(result.error);
+ }
+ if (result.value != 1 || c == '\n')
+ break;
+ }
+ }
+
+ bool has_error = f->error_unlocked();
+ bool has_eof = f->iseof_unlocked();
+ f->unlock();
+
+ if (has_error)
+ return Error(EIO);
+
+ if (bytes_read == 0 && has_eof)
+ return ReadLineResult{0, false};
+
+ buf[bytes_read] = '\0';
+ return ReadLineResult{bytes_read, truncated};
+}
+
+LLVM_LIBC_FUNCTION(struct passwd *, getpwent, ()) {
+ if (!pwd_file) {
+ auto result = openfile(pwd_file_path, "r");
+ if (!result.has_value()) {
+ libc_errno = result.error();
+ return nullptr;
+ }
+ pwd_file = result.value();
+ }
+
+ while (true) {
+ auto result = read_line(pwd_file, line_buffer);
+ if (!result.has_value()) {
+ libc_errno = result.error();
+ return nullptr;
+ }
+
+ ReadLineResult res = result.value();
+ if (res.bytes_read == 0)
+ return nullptr;
+
+ if (res.truncated) {
+ libc_errno = EINVAL;
+ return nullptr;
+ }
+
+ size_t len = res.bytes_read;
+ if (len > 0 && line_buffer[len - 1] == '\n')
+ line_buffer[len - 1] = '\0';
+
+ auto passwd_or = internal::parse_passwd_line(line_buffer);
+ if (!passwd_or.has_value()) {
+ libc_errno = passwd_or.error();
+ return nullptr;
+ }
+
+ pwd_entry = passwd_or.value();
+ return &pwd_entry;
+ }
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/getpwent.h b/libc/src/pwd/getpwent.h
new file mode 100644
index 0000000000000..d116ee0376433
--- /dev/null
+++ b/libc/src/pwd/getpwent.h
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwent function and internal helpers.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_GETPWENT_H
+#define LLVM_LIBC_SRC_PWD_GETPWENT_H
+
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// Overrides the default password file path for testing purposes.
+void set_passwd_path(const char *path);
+
+} // namespace internal
+
+// Internal helper function to open or rewind the password file.
+ErrorOr<int> setpwent_impl();
+
+// Internal helper function to close the password file.
+ErrorOr<int> endpwent_impl();
+
+// Reads the next entry from the password database.
+struct passwd *getpwent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_GETPWENT_H
diff --git a/libc/src/pwd/setpwent.cpp b/libc/src/pwd/setpwent.cpp
new file mode 100644
index 0000000000000..c5817482591ef
--- /dev/null
+++ b/libc/src/pwd/setpwent.cpp
@@ -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
+/// Implementation of setpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/setpwent.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/pwd/getpwent.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, setpwent, ()) {
+ // setpwent_impl opens or rewinds the password file. If an error occurs,
+ // it returns an Error with an errno value which is set here.
+ auto res = setpwent_impl();
+ if (!res.has_value())
+ libc_errno = res.error();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/setpwent.h b/libc/src/pwd/setpwent.h
new file mode 100644
index 0000000000000..90dda5f9c7c2b
--- /dev/null
+++ b/libc/src/pwd/setpwent.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 setpwent function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_SETPWENT_H
+#define LLVM_LIBC_SRC_PWD_SETPWENT_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Rewinds the password database stream to the beginning.
+void setpwent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_SETPWENT_H
diff --git a/libc/test/src/pwd/CMakeLists.txt b/libc/test/src/pwd/CMakeLists.txt
index eb6c96f2f296f..254d740bc05ee 100644
--- a/libc/test/src/pwd/CMakeLists.txt
+++ b/libc/test/src/pwd/CMakeLists.txt
@@ -11,3 +11,21 @@ add_libc_unittest(
libc.hdr.types.struct_passwd
libc.src.pwd.pwd_utils
)
+
+add_libc_unittest(
+ getpwent_test
+ SUITE
+ libc_pwd_unittests
+ SRCS
+ getpwent_test.cpp
+ DEPENDS
+ libc.hdr.types.struct_passwd
+ libc.src.__support.File.file
+ libc.src.__support.File.platform_file
+ libc.src.errno.errno
+ libc.src.pwd.endpwent
+ libc.src.pwd.getpwent
+ libc.src.pwd.pwd_utils
+ libc.src.pwd.setpwent
+ libc.src.stdio.remove
+)
diff --git a/libc/test/src/pwd/getpwent_test.cpp b/libc/test/src/pwd/getpwent_test.cpp
new file mode 100644
index 0000000000000..67c7bb19508a1
--- /dev/null
+++ b/libc/test/src/pwd/getpwent_test.cpp
@@ -0,0 +1,152 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwent, setpwent, and endpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/File/file.h"
+#include "src/__support/libc_errno.h"
+#include "src/pwd/endpwent.h"
+#include "src/pwd/getpwent.h"
+#include "src/pwd/pwd_utils.h"
+#include "src/pwd/setpwent.h"
+#include "src/stdio/remove.h"
+#include "test/UnitTest/Test.h"
+
+namespace {
+
+// RAII helper class for creating and automatically removing temporary test
+// files.
+class HermeticFile {
+ char path[256];
+
+public:
+ HermeticFile(const char *file_path, const char *content) {
+ size_t i = 0;
+ for (; file_path[i] && i < sizeof(path) - 1; ++i)
+ path[i] = file_path[i];
+ path[i] = '\0';
+
+ auto file_or = LIBC_NAMESPACE::openfile(path, "w");
+ if (file_or.has_value()) {
+ auto *f = file_or.value();
+ size_t len = 0;
+ for (const char *p = content; *p; ++p)
+ ++len;
+ f->write(content, len);
+ f->close();
+ }
+ }
+
+ ~HermeticFile() { LIBC_NAMESPACE::remove(path); }
+
+ const char *get_path() const { return path; }
+};
+
+} // namespace
+
+TEST(LlvmLibcPwdTest, GetPwentTestSuccess) {
+ const char *content = "root:x:0:0:root:/root:/bin/bash\n"
+ "bin:x:1:1:bin:/bin:/sbin/nologin\n";
+ HermeticFile test_file(libc_make_test_file_path("getpwent_success.test"),
+ content);
+
+ LIBC_NAMESPACE::internal::set_passwd_path(test_file.get_path());
+ LIBC_NAMESPACE::setpwent();
+
+ struct passwd *pwd1 = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd1 != nullptr);
+ ASSERT_STREQ(pwd1->pw_name, "root");
+ ASSERT_EQ(pwd1->pw_uid, 0u);
+
+ struct passwd *pwd2 = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd2 != nullptr);
+ ASSERT_STREQ(pwd2->pw_name, "bin");
+ ASSERT_EQ(pwd2->pw_uid, 1u);
+
+ struct passwd *pwd3 = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd3 == nullptr);
+
+ LIBC_NAMESPACE::endpwent();
+}
+
+TEST(LlvmLibcPwdTest, GetPwentTestFailure) {
+ const char *content = "invalid_line_without_enough_fields\n";
+ HermeticFile test_file(libc_make_test_file_path("getpwent_fail.test"),
+ content);
+
+ LIBC_NAMESPACE::internal::set_passwd_path(test_file.get_path());
+ LIBC_NAMESPACE::setpwent();
+
+ libc_errno = 0;
+ struct passwd *pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd == nullptr);
+ ASSERT_EQ(static_cast<int>(libc_errno), EINVAL);
+
+ LIBC_NAMESPACE::endpwent();
+}
+
+TEST(LlvmLibcPwdTest, SetPwentTestHermetic) {
+ const char *content = "user1:x:1000:1000:User One:/home/user1:/bin/bash\n"
+ "user2:x:1001:1001:User Two:/home/user2:/bin/bash\n";
+ HermeticFile test_file(libc_make_test_file_path("setpwent_hermetic.test"),
+ content);
+
+ LIBC_NAMESPACE::internal::set_passwd_path(test_file.get_path());
+
+ struct passwd *pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd != nullptr);
+ ASSERT_STREQ(pwd->pw_name, "user1");
+
+ pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd != nullptr);
+ ASSERT_STREQ(pwd->pw_name, "user2");
+
+ // Reset iteration
+ LIBC_NAMESPACE::setpwent();
+
+ pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd != nullptr);
+ ASSERT_STREQ(pwd->pw_name, "user1");
+
+ LIBC_NAMESPACE::endpwent();
+}
+
+TEST(LlvmLibcPwdTest, ReopenAfterEndpwent) {
+ const char *content = "root:x:0:0:root:/root:/bin/bash\n";
+ HermeticFile test_file(libc_make_test_file_path("reopen_endpwent.test"),
+ content);
+
+ LIBC_NAMESPACE::internal::set_passwd_path(test_file.get_path());
+
+ struct passwd *pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd != nullptr);
+ ASSERT_STREQ(pwd->pw_name, "root");
+
+ LIBC_NAMESPACE::endpwent();
+
+ pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd != nullptr);
+ ASSERT_STREQ(pwd->pw_name, "root");
+
+ LIBC_NAMESPACE::endpwent();
+}
+
+TEST(LlvmLibcPwdTest, FileOpenFailure) {
+ LIBC_NAMESPACE::internal::set_passwd_path(
+ "/nonexistent_directory/nonexistent_file");
+ LIBC_NAMESPACE::endpwent(); // Force close any existing file
+
+ libc_errno = 0;
+ struct passwd *pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_TRUE(pwd == nullptr);
+ ASSERT_EQ(static_cast<int>(libc_errno), ENOENT);
+}
More information about the libc-commits
mailing list