[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