[libc-commits] [libc] [libc] Add getpwent, setpwent, and endpwent functions (PR #206064)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Fri Jul 3 09:54:45 PDT 2026


https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/206064

>From 48364972057367a05536a73a9bae6e900e67ca8e Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 26 Jun 2026 12:42:06 +0100
Subject: [PATCH 1/4] [libc] Add getpwent, setpwent, and endpwent functions

Implemented `getpwent`, `setpwent`, and `endpwent` functions for
accessing the password database.

* Added `struct passwd` definition in `llvm-libc-types`.
* Added `pwd.yaml` interface definition with POSIX standards.
* Implemented `getpwent`, `setpwent`, and `endpwent` entrypoints.
* Implemented `getpwent` to robustly skip malformed lines by looping.
* Implemented passwd line parsing in an internal helper
  `parse_passwd_line` in `pwd_utils`.
* Used `strtointeger` for robust parsing of UID and GID fields.
* Added `struct_passwd` proxy header to avoid direct system includes.
* Centrally declared internal implementation helpers in `pwd_utils.h`.
* Added unit tests for `getpwent` and `setpwent` / `endpwent` behaviour.

Assisted-by: Automated tooling, human reviewed.
---
 libc/config/linux/aarch64/entrypoints.txt    |  5 ++
 libc/config/linux/arm/entrypoints.txt        |  5 ++
 libc/config/linux/riscv/entrypoints.txt      |  5 ++
 libc/config/linux/x86_64/entrypoints.txt     |  5 ++
 libc/hdr/types/CMakeLists.txt                |  8 +++
 libc/hdr/types/struct_passwd.h               | 27 +++++++
 libc/include/CMakeLists.txt                  | 12 ++++
 libc/include/llvm-libc-types/CMakeLists.txt  |  5 ++
 libc/include/llvm-libc-types/struct_passwd.h | 30 ++++++++
 libc/include/pwd.yaml                        | 27 +++++++
 libc/src/CMakeLists.txt                      |  1 +
 libc/src/pwd/CMakeLists.txt                  | 62 ++++++++++++++++
 libc/src/pwd/endpwent.cpp                    | 23 ++++++
 libc/src/pwd/endpwent.h                      | 25 +++++++
 libc/src/pwd/getpwent.cpp                    | 65 +++++++++++++++++
 libc/src/pwd/getpwent.h                      | 26 +++++++
 libc/src/pwd/pwd_utils.cpp                   | 75 ++++++++++++++++++++
 libc/src/pwd/pwd_utils.h                     | 32 +++++++++
 libc/src/pwd/setpwent.cpp                    | 23 ++++++
 libc/src/pwd/setpwent.h                      | 25 +++++++
 libc/test/src/CMakeLists.txt                 |  1 +
 libc/test/src/pwd/CMakeLists.txt             | 22 ++++++
 libc/test/src/pwd/getpwent_test.cpp          | 66 +++++++++++++++++
 23 files changed, 575 insertions(+)
 create mode 100644 libc/hdr/types/struct_passwd.h
 create mode 100644 libc/include/llvm-libc-types/struct_passwd.h
 create mode 100644 libc/include/pwd.yaml
 create mode 100644 libc/src/pwd/CMakeLists.txt
 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/pwd_utils.cpp
 create mode 100644 libc/src/pwd/pwd_utils.h
 create mode 100644 libc/src/pwd/setpwent.cpp
 create mode 100644 libc/src/pwd/setpwent.h
 create mode 100644 libc/test/src/pwd/CMakeLists.txt
 create mode 100644 libc/test/src/pwd/getpwent_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 570f7666ba67b..fa55eba8f0321 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -35,6 +35,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.sched_get_priority_max
     libc.src.sched.sched_get_priority_min
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index e6aa6091ed550..f55126f9c321c 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -23,6 +23,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
+
     # string.h entrypoints
     libc.src.string.memccpy
     libc.src.string.memchr
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 2508de5cfe5a4..7d3cfd77dd688 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -35,6 +35,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.sched_get_priority_max
     libc.src.sched.sched_get_priority_min
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 5ace968a739c8..251957896ff2e 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -48,6 +48,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/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index a130f7ee0000a..93eb08e1f3620 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -1127,3 +1127,11 @@ add_proxy_header_library(
   FULL_BUILD_DEPENDS
     libc.include.llvm-libc-types.regmatch_t
 )
+
+add_proxy_header_library(
+  struct_passwd
+  HDRS
+    struct_passwd.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.struct_passwd
+)
diff --git a/libc/hdr/types/struct_passwd.h b/libc/hdr/types/struct_passwd.h
new file mode 100644
index 0000000000000..e55667e7172d2
--- /dev/null
+++ b/libc/hdr/types/struct_passwd.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 passwd.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_PASSWD_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_PASSWD_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_passwd.h"
+
+#else
+
+#include <pwd.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_PASSWD_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index bb670b614742a..c740dae56f620 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -1034,6 +1034,18 @@ add_header_macro(
     .llvm-libc-types.nl_catd
   )
 
+add_header_macro(
+  pwd
+  ../libc/include/pwd.yaml
+  pwd.h
+  DEPENDS
+    .llvm_libc_common_h
+    .llvm-libc-types.struct_passwd
+    .llvm-libc-types.uid_t
+    .llvm-libc-types.gid_t
+    .llvm-libc-types.size_t
+)
+
 # UEFI spec references "Uefi.h" so we use that name for compatibility
 add_header_macro(
   uefi
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 0512d3f0e642a..eeff097d6ceec 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -178,6 +178,11 @@ add_header(
     .dev_t .ino_t .mode_t .nlink_t .uid_t .gid_t .off_t .struct_timespec
     .blksize_t .blkcnt_t
 )
+add_header(
+  struct_passwd
+  HDR struct_passwd.h
+  DEPENDS .gid_t .uid_t
+)
 add_header(struct_tm HDR struct_tm.h)
 add_header(struct_utsname HDR struct_utsname.h)
 add_header(thrd_start_t HDR thrd_start_t.h)
diff --git a/libc/include/llvm-libc-types/struct_passwd.h b/libc/include/llvm-libc-types/struct_passwd.h
new file mode 100644
index 0000000000000..7b41be6edbbe0
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct_passwd.h
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Definition of struct passwd.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef __LLVM_LIBC_TYPES_STRUCT_PASSWD_H__
+#define __LLVM_LIBC_TYPES_STRUCT_PASSWD_H__
+
+#include "gid_t.h"
+#include "uid_t.h"
+
+struct passwd {
+  char *pw_name;
+  char *pw_passwd;
+  uid_t pw_uid;
+  gid_t pw_gid;
+  char *pw_gecos;
+  char *pw_dir;
+  char *pw_shell;
+};
+
+#endif // __LLVM_LIBC_TYPES_STRUCT_PASSWD_H__
diff --git a/libc/include/pwd.yaml b/libc/include/pwd.yaml
new file mode 100644
index 0000000000000..f8a9500937ed1
--- /dev/null
+++ b/libc/include/pwd.yaml
@@ -0,0 +1,27 @@
+header: pwd.h
+standards:
+  - posix
+macros: []
+types:
+  - type_name: struct_passwd
+  - type_name: uid_t
+  - type_name: gid_t
+  - type_name: size_t
+enums: []
+objects: []
+functions:
+  - name: getpwent
+    standards:
+      - posix
+    return_type: struct passwd *
+    arguments: []
+  - name: setpwent
+    standards:
+      - posix
+    return_type: void
+    arguments: []
+  - name: endpwent
+    standards:
+      - posix
+    return_type: void
+    arguments: []
diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt
index 62aa164b35ee7..a269ac4a83ffd 100644
--- a/libc/src/CMakeLists.txt
+++ b/libc/src/CMakeLists.txt
@@ -28,6 +28,7 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
   add_subdirectory(dirent)
   add_subdirectory(fcntl)
   add_subdirectory(poll)
+  add_subdirectory(pwd)
   add_subdirectory(pthread)
   add_subdirectory(sched)
   add_subdirectory(semaphore)
diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
new file mode 100644
index 0000000000000..b808f59492e11
--- /dev/null
+++ b/libc/src/pwd/CMakeLists.txt
@@ -0,0 +1,62 @@
+#===-- CMakeLists.txt ----------------------------------------------------===#
+#
+# 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
+#
+#===----------------------------------------------------------------------===#
+
+add_object_library(
+  pwd_utils
+  SRCS
+    pwd_utils.cpp
+  HDRS
+    pwd_utils.h
+  DEPENDS
+    libc.hdr.types.struct_passwd
+    libc.src.__support.str_to_integer
+    libc.src.string.string_utils
+)
+
+add_entrypoint_object(
+  getpwent
+  SRCS
+    getpwent.cpp
+  HDRS
+    getpwent.h
+  DEPENDS
+    libc.include.pwd
+    libc.hdr.types.struct_passwd
+    libc.src.errno.errno
+    libc.src.stdio.fopen
+    libc.src.stdio.fclose
+    libc.src.stdio.fgets
+    libc.src.stdio.fseek
+    libc.src.string.string_utils
+    libc.hdr.stdio_macros
+    .pwd_utils
+)
+
+add_entrypoint_object(
+  setpwent
+  SRCS
+    setpwent.cpp
+  HDRS
+    setpwent.h
+  DEPENDS
+    libc.include.pwd
+    .getpwent
+    .pwd_utils
+)
+
+add_entrypoint_object(
+  endpwent
+  SRCS
+    endpwent.cpp
+  HDRS
+    endpwent.h
+  DEPENDS
+    libc.include.pwd
+    .getpwent
+    .pwd_utils
+)
diff --git a/libc/src/pwd/endpwent.cpp b/libc/src/pwd/endpwent.cpp
new file mode 100644
index 0000000000000..948eac2b72349
--- /dev/null
+++ b/libc/src/pwd/endpwent.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/macros/config.h"
+#include "src/pwd/pwd_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, endpwent, ()) { endpwent_impl(); }
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/endpwent.h b/libc/src/pwd/endpwent.h
new file mode 100644
index 0000000000000..b5a8262cc7d33
--- /dev/null
+++ b/libc/src/pwd/endpwent.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 endpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_ENDPWENT_H
+#define LLVM_LIBC_SRC_PWD_ENDPWENT_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+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..83bbe1ade4137
--- /dev/null
+++ b/libc/src/pwd/getpwent.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/pwd/pwd_utils.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/fgets.h"
+#include "src/stdio/fopen.h"
+#include "src/string/string_utils.h"
+
+#include "hdr/stdio_macros.h"
+#include "src/stdio/fseek.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+static FILE *pwd_file = nullptr;
+static char line_buffer[1024];
+static struct passwd pwd_entry;
+
+void setpwent_impl() {
+  if (pwd_file)
+    LIBC_NAMESPACE::fseek(pwd_file, 0, SEEK_SET);
+}
+
+void endpwent_impl() {
+  if (pwd_file) {
+    LIBC_NAMESPACE::fclose(pwd_file);
+    pwd_file = nullptr;
+  }
+}
+
+LLVM_LIBC_FUNCTION(struct passwd *, getpwent, ()) {
+  if (!pwd_file) {
+    pwd_file = LIBC_NAMESPACE::fopen("/etc/passwd", "r");
+    if (!pwd_file)
+      return nullptr;
+  }
+
+  while (LIBC_NAMESPACE::fgets(line_buffer, sizeof(line_buffer), pwd_file)) {
+    // Remove newline
+    size_t len = LIBC_NAMESPACE::internal::string_length(line_buffer);
+    if (len > 0 && line_buffer[len - 1] == '\n')
+      line_buffer[len - 1] = '\0';
+
+    if (internal::parse_passwd_line(line_buffer, &pwd_entry))
+      return &pwd_entry;
+  }
+
+  return nullptr;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/getpwent.h b/libc/src/pwd/getpwent.h
new file mode 100644
index 0000000000000..5e8e96b8f6ba1
--- /dev/null
+++ b/libc/src/pwd/getpwent.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
+/// Declarations of getpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_GETPWENT_H
+#define LLVM_LIBC_SRC_PWD_GETPWENT_H
+
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct passwd *getpwent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_GETPWENT_H
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
new file mode 100644
index 0000000000000..6a993c1129c5a
--- /dev/null
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Helper functions for pwd.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/pwd_utils.h"
+#include "src/__support/str_to_integer.h"
+#include "src/string/string_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+bool parse_passwd_line(char *line, struct passwd *pwd) {
+  if (!line || !pwd)
+    return false;
+
+  char *context = line;
+
+  pwd->pw_name =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!pwd->pw_name)
+    return false;
+
+  pwd->pw_passwd =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!pwd->pw_passwd)
+    return false;
+
+  char *uid_str =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!uid_str)
+    return false;
+  auto uid_res = LIBC_NAMESPACE::internal::strtointeger<uid_t>(uid_str, 10);
+  if (uid_res.has_error())
+    return false;
+  pwd->pw_uid = uid_res;
+
+  char *gid_str =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!gid_str)
+    return false;
+  auto gid_res = LIBC_NAMESPACE::internal::strtointeger<gid_t>(gid_str, 10);
+  if (gid_res.has_error())
+    return false;
+  pwd->pw_gid = gid_res;
+
+  pwd->pw_gecos =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!pwd->pw_gecos)
+    return false;
+
+  pwd->pw_dir =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!pwd->pw_dir)
+    return false;
+
+  // shell
+  pwd->pw_shell =
+      LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
+  if (!pwd->pw_shell)
+    return false;
+
+  return true;
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/pwd_utils.h b/libc/src/pwd/pwd_utils.h
new file mode 100644
index 0000000000000..2ff5b3211dbb6
--- /dev/null
+++ b/libc/src/pwd/pwd_utils.h
@@ -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
+/// Declarations of helper functions for pwd.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_PWD_UTILS_H
+#define LLVM_LIBC_SRC_PWD_PWD_UTILS_H
+
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+bool parse_passwd_line(char *line, struct passwd *pwd);
+
+} // namespace internal
+
+void setpwent_impl();
+void endpwent_impl();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_PWD_UTILS_H
diff --git a/libc/src/pwd/setpwent.cpp b/libc/src/pwd/setpwent.cpp
new file mode 100644
index 0000000000000..a4efcdacba2dc
--- /dev/null
+++ b/libc/src/pwd/setpwent.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/macros/config.h"
+#include "src/pwd/pwd_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, setpwent, ()) { setpwent_impl(); }
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/setpwent.h b/libc/src/pwd/setpwent.h
new file mode 100644
index 0000000000000..91cb263b41d90
--- /dev/null
+++ b/libc/src/pwd/setpwent.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 setpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_SETPWENT_H
+#define LLVM_LIBC_SRC_PWD_SETPWENT_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void setpwent();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_SETPWENT_H
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index 45815c9bba8ca..c0617d83c30ab 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -87,6 +87,7 @@ add_subdirectory(inttypes)
 if(${LIBC_TARGET_OS} STREQUAL "linux")
   add_subdirectory(fcntl)
   add_subdirectory(poll)
+  add_subdirectory(pwd)
   add_subdirectory(sched)
   add_subdirectory(sys)
   add_subdirectory(termios)
diff --git a/libc/test/src/pwd/CMakeLists.txt b/libc/test/src/pwd/CMakeLists.txt
new file mode 100644
index 0000000000000..b5bf000443b38
--- /dev/null
+++ b/libc/test/src/pwd/CMakeLists.txt
@@ -0,0 +1,22 @@
+#===-- CMakeLists.txt ----------------------------------------------------===#
+#
+# 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
+#
+#===----------------------------------------------------------------------===#
+
+add_custom_target(libc_pwd_unittests)
+
+add_libc_unittest(
+  getpwent_test
+  SUITE
+    libc_pwd_unittests
+  SRCS
+    getpwent_test.cpp
+  DEPENDS
+    libc.src.pwd.getpwent
+    libc.src.pwd.setpwent
+    libc.src.pwd.endpwent
+    libc.src.string.strcmp
+)
diff --git a/libc/test/src/pwd/getpwent_test.cpp b/libc/test/src/pwd/getpwent_test.cpp
new file mode 100644
index 0000000000000..eabc09e502f69
--- /dev/null
+++ b/libc/test/src/pwd/getpwent_test.cpp
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwent.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/endpwent.h"
+#include "src/pwd/getpwent.h"
+#include "src/pwd/setpwent.h"
+#include "src/string/strcmp.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcPwdTest, GetPwentTest) {
+  // We assume /etc/passwd exists and contains at least "root" and "daemon".
+  // We also assume it does NOT contain "baduser".
+
+  bool found_root = false;
+  bool found_daemon = false;
+  bool found_baduser = false;
+
+  LIBC_NAMESPACE::setpwent();
+
+  struct passwd *pw;
+  while ((pw = LIBC_NAMESPACE::getpwent()) != nullptr) {
+    if (LIBC_NAMESPACE::strcmp(pw->pw_name, "root") == 0) {
+      found_root = true;
+      ASSERT_EQ(pw->pw_uid, static_cast<uid_t>(0));
+    }
+    if (LIBC_NAMESPACE::strcmp(pw->pw_name, "daemon") == 0) {
+      found_daemon = true;
+    }
+    if (LIBC_NAMESPACE::strcmp(pw->pw_name, "baduser") == 0) {
+      found_baduser = true;
+    }
+  }
+
+  ASSERT_TRUE(found_root);
+  ASSERT_TRUE(found_daemon);
+  ASSERT_FALSE(found_baduser);
+
+  LIBC_NAMESPACE::endpwent();
+}
+
+TEST(LlvmLibcPwdTest, SetPwentTest) {
+  // Read first entry
+  LIBC_NAMESPACE::setpwent();
+  struct passwd *pw1 = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw1 != nullptr);
+
+  // Rewind
+  LIBC_NAMESPACE::setpwent();
+  struct passwd *pw2 = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw2 != nullptr);
+
+  // Should be the same entry
+  ASSERT_STREQ(pw1->pw_name, pw2->pw_name);
+
+  LIBC_NAMESPACE::endpwent();
+}

>From 20b48bd4e24ccc35dd731eea7ef025f363e71e70 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 3 Jul 2026 16:17:34 +0100
Subject: [PATCH 2/4] [libc] Use internal File class in getpwent

Rewrite getpwent to use the internal File class instead of public
entrypoints (fopen, fgets, fclose, fseek). This resolves link errors
in overlay mode where these stdio entrypoints are not built.

Also update struct_passwd.h type header guard to remove double
underscores.

Assisted-by: Automated tooling, human reviewed.
---
 libc/include/llvm-libc-types/struct_passwd.h |  6 +--
 libc/src/pwd/CMakeLists.txt                  |  6 +--
 libc/src/pwd/getpwent.cpp                    | 49 ++++++++++++++++----
 3 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/libc/include/llvm-libc-types/struct_passwd.h b/libc/include/llvm-libc-types/struct_passwd.h
index 7b41be6edbbe0..0f0baf743627b 100644
--- a/libc/include/llvm-libc-types/struct_passwd.h
+++ b/libc/include/llvm-libc-types/struct_passwd.h
@@ -11,8 +11,8 @@
 ///
 //===----------------------------------------------------------------------===//
 
-#ifndef __LLVM_LIBC_TYPES_STRUCT_PASSWD_H__
-#define __LLVM_LIBC_TYPES_STRUCT_PASSWD_H__
+#ifndef LLVM_LIBC_TYPES_STRUCT_PASSWD_H
+#define LLVM_LIBC_TYPES_STRUCT_PASSWD_H
 
 #include "gid_t.h"
 #include "uid_t.h"
@@ -27,4 +27,4 @@ struct passwd {
   char *pw_shell;
 };
 
-#endif // __LLVM_LIBC_TYPES_STRUCT_PASSWD_H__
+#endif // LLVM_LIBC_TYPES_STRUCT_PASSWD_H
diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
index b808f59492e11..0d1706bd81d03 100644
--- a/libc/src/pwd/CMakeLists.txt
+++ b/libc/src/pwd/CMakeLists.txt
@@ -28,10 +28,8 @@ add_entrypoint_object(
     libc.include.pwd
     libc.hdr.types.struct_passwd
     libc.src.errno.errno
-    libc.src.stdio.fopen
-    libc.src.stdio.fclose
-    libc.src.stdio.fgets
-    libc.src.stdio.fseek
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
     libc.src.string.string_utils
     libc.hdr.stdio_macros
     .pwd_utils
diff --git a/libc/src/pwd/getpwent.cpp b/libc/src/pwd/getpwent.cpp
index 83bbe1ade4137..7e6982101a6c2 100644
--- a/libc/src/pwd/getpwent.cpp
+++ b/libc/src/pwd/getpwent.cpp
@@ -12,44 +12,73 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/pwd/getpwent.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 "src/stdio/fclose.h"
-#include "src/stdio/fgets.h"
-#include "src/stdio/fopen.h"
 #include "src/string/string_utils.h"
 
 #include "hdr/stdio_macros.h"
-#include "src/stdio/fseek.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-static FILE *pwd_file = nullptr;
+static File *pwd_file = nullptr;
 static char line_buffer[1024];
 static struct passwd pwd_entry;
 
 void setpwent_impl() {
   if (pwd_file)
-    LIBC_NAMESPACE::fseek(pwd_file, 0, SEEK_SET);
+    pwd_file->seek(0, SEEK_SET);
 }
 
 void endpwent_impl() {
   if (pwd_file) {
-    LIBC_NAMESPACE::fclose(pwd_file);
+    pwd_file->close();
     pwd_file = nullptr;
   }
 }
 
+static bool read_line(File *f, char *buf, size_t max_len) {
+  if (max_len < 1)
+    return false;
+
+  unsigned char c = '\0';
+  f->lock();
+  size_t i = 0;
+  FileIOResult result(0);
+  for (; i < (max_len - 1) && c != '\n'; ++i) {
+    result = f->read_unlocked(&c, 1);
+    if (result.has_error()) {
+      libc_errno = result.error;
+      break;
+    }
+    if (result.value != 1)
+      break;
+    buf[i] = c;
+  }
+  bool has_error = f->error_unlocked();
+  bool has_eof = f->iseof_unlocked();
+  f->unlock();
+
+  if (has_error || (i == 0 && has_eof))
+    return false;
+
+  buf[i] = '\0';
+  return true;
+}
+
 LLVM_LIBC_FUNCTION(struct passwd *, getpwent, ()) {
   if (!pwd_file) {
-    pwd_file = LIBC_NAMESPACE::fopen("/etc/passwd", "r");
-    if (!pwd_file)
+    auto result = LIBC_NAMESPACE::openfile("/etc/passwd", "r");
+    if (!result.has_value()) {
+      libc_errno = result.error();
       return nullptr;
+    }
+    pwd_file = result.value();
   }
 
-  while (LIBC_NAMESPACE::fgets(line_buffer, sizeof(line_buffer), pwd_file)) {
+  while (read_line(pwd_file, line_buffer, sizeof(line_buffer))) {
     // Remove newline
     size_t len = LIBC_NAMESPACE::internal::string_length(line_buffer);
     if (len > 0 && line_buffer[len - 1] == '\n')

>From b93b1ceff55c91f89eb2151a772c555bfda1a62e Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 3 Jul 2026 17:43:10 +0100
Subject: [PATCH 3/4] [libc] Add comment explaining getpwent helper design

Explain why setpwent_impl and endpwent_impl are defined in getpwent.cpp
instead of directly in their entrypoint files.

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/pwd/getpwent.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libc/src/pwd/getpwent.cpp b/libc/src/pwd/getpwent.cpp
index 7e6982101a6c2..c275483d3b22f 100644
--- a/libc/src/pwd/getpwent.cpp
+++ b/libc/src/pwd/getpwent.cpp
@@ -27,6 +27,11 @@ static File *pwd_file = nullptr;
 static char line_buffer[1024];
 static struct passwd pwd_entry;
 
+// Helper implementations for setpwent and endpwent.
+// These are defined here because they need to access the static pwd_file state,
+// which is encapsulated in this file. The actual entrypoints are defined in
+// separate files (setpwent.cpp, endpwent.cpp) to comply with LLVM-libc's
+// one-function-per-file rule.
 void setpwent_impl() {
   if (pwd_file)
     pwd_file->seek(0, SEEK_SET);

>From 1cbfd138a8fbbae94b8c58ac547b33747d066e9f Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 3 Jul 2026 17:54:31 +0100
Subject: [PATCH 4/4] [libc] Fix code review findings for pwd

Address review comments and improve implementation quality:

- Remove redundant comment in pwd_utils.cpp.
- Propagate errors from read_line helper and set errno at the edge in getpwent.
- Set errno on setpwent and endpwent failures by checking seek/close returns.
- Handle long lines (>1023 chars) in getpwent by truncating and discarding.
- Add set_passwd_path helper for hermetic testing.
- Rewrite getpwent_test to use hermetic test files and fix SetPwentTest bug.
- Add pwd_utils_test to directly test parse_passwd_line.
- Fix CMake dependencies for pwd targets and tests.
- Improve parse_passwd_line to strictly validate UID and GID fields.

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/pwd/CMakeLists.txt          |   8 ++
 libc/src/pwd/endpwent.cpp            |   7 +-
 libc/src/pwd/getpwent.cpp            | 102 ++++++++++++++----
 libc/src/pwd/pwd_utils.cpp           |  11 +-
 libc/src/pwd/pwd_utils.h             |   5 +-
 libc/src/pwd/setpwent.cpp            |   7 +-
 libc/test/src/pwd/CMakeLists.txt     |  16 +++
 libc/test/src/pwd/getpwent_test.cpp  | 148 +++++++++++++++++++++------
 libc/test/src/pwd/pwd_utils_test.cpp |  65 ++++++++++++
 9 files changed, 313 insertions(+), 56 deletions(-)
 create mode 100644 libc/test/src/pwd/pwd_utils_test.cpp

diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
index 0d1706bd81d03..87b363fea2e8f 100644
--- a/libc/src/pwd/CMakeLists.txt
+++ b/libc/src/pwd/CMakeLists.txt
@@ -28,6 +28,8 @@ add_entrypoint_object(
     libc.include.pwd
     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.src.string.string_utils
@@ -43,6 +45,9 @@ add_entrypoint_object(
     setpwent.h
   DEPENDS
     libc.include.pwd
+    libc.src.errno.errno
+    libc.src.__support.common
+    libc.src.__support.macros.config
     .getpwent
     .pwd_utils
 )
@@ -55,6 +60,9 @@ add_entrypoint_object(
     endpwent.h
   DEPENDS
     libc.include.pwd
+    libc.src.errno.errno
+    libc.src.__support.common
+    libc.src.__support.macros.config
     .getpwent
     .pwd_utils
 )
diff --git a/libc/src/pwd/endpwent.cpp b/libc/src/pwd/endpwent.cpp
index 948eac2b72349..948708a77a932 100644
--- a/libc/src/pwd/endpwent.cpp
+++ b/libc/src/pwd/endpwent.cpp
@@ -13,11 +13,16 @@
 
 #include "src/pwd/endpwent.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include "src/pwd/pwd_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-LLVM_LIBC_FUNCTION(void, endpwent, ()) { endpwent_impl(); }
+LLVM_LIBC_FUNCTION(void, endpwent, ()) {
+  int result = endpwent_impl();
+  if (result != 0)
+    libc_errno = result;
+}
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/getpwent.cpp b/libc/src/pwd/getpwent.cpp
index c275483d3b22f..d5d2958bf5ec9 100644
--- a/libc/src/pwd/getpwent.cpp
+++ b/libc/src/pwd/getpwent.cpp
@@ -21,61 +21,116 @@
 
 #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;
 static char line_buffer[1024];
 static struct passwd pwd_entry;
 
+namespace internal {
+void set_passwd_path(const char *path) {
+  if (pwd_file) {
+    pwd_file->close();
+    pwd_file = nullptr;
+  }
+  pwd_file_path = path;
+}
+} // namespace internal
+
 // Helper implementations for setpwent and endpwent.
 // These are defined here because they need to access the static pwd_file state,
 // which is encapsulated in this file. The actual entrypoints are defined in
 // separate files (setpwent.cpp, endpwent.cpp) to comply with LLVM-libc's
 // one-function-per-file rule.
-void setpwent_impl() {
-  if (pwd_file)
-    pwd_file->seek(0, SEEK_SET);
+int setpwent_impl() {
+  if (!pwd_file) {
+    auto result = LIBC_NAMESPACE::openfile(pwd_file_path, "r");
+    if (!result.has_value())
+      return result.error();
+    pwd_file = result.value();
+  } else {
+    auto result = pwd_file->seek(0, SEEK_SET);
+    if (!result.has_value())
+      return result.error();
+  }
+  return 0;
 }
 
-void endpwent_impl() {
+int endpwent_impl() {
   if (pwd_file) {
-    pwd_file->close();
+    int result = pwd_file->close();
     pwd_file = nullptr;
+    return result;
   }
+  return 0;
 }
 
-static bool read_line(File *f, char *buf, size_t max_len) {
+struct ReadLineResult {
+  size_t bytes_read;
+  bool truncated;
+};
+
+static ErrorOr<ReadLineResult> read_line(File *f, char *buf, size_t max_len) {
   if (max_len < 1)
-    return false;
+    return Error(EINVAL);
 
   unsigned char c = '\0';
   f->lock();
   size_t i = 0;
   FileIOResult result(0);
-  for (; i < (max_len - 1) && c != '\n'; ++i) {
+  bool truncated = false;
+  for (; i < (max_len - 1); ++i) {
     result = f->read_unlocked(&c, 1);
     if (result.has_error()) {
-      libc_errno = result.error;
-      break;
+      f->unlock();
+      return Error(result.error);
     }
     if (result.value != 1)
       break;
     buf[i] = c;
+    if (c == '\n') {
+      i++; // Include '\n' in bytes_read count
+      break;
+    }
   }
+
+  // Check for truncation
+  if (i == max_len - 1 && c != '\n') {
+    truncated = true;
+    // Discard the rest of the line
+    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 || (i == 0 && has_eof))
-    return false;
+  if (has_error)
+    return Error(EIO);
+
+  if (i == 0 && has_eof)
+    return ReadLineResult{0, false};
 
   buf[i] = '\0';
-  return true;
+  return ReadLineResult{i, truncated};
 }
 
 LLVM_LIBC_FUNCTION(struct passwd *, getpwent, ()) {
   if (!pwd_file) {
-    auto result = LIBC_NAMESPACE::openfile("/etc/passwd", "r");
+    auto result = LIBC_NAMESPACE::openfile(pwd_file_path, "r");
     if (!result.has_value()) {
       libc_errno = result.error();
       return nullptr;
@@ -83,17 +138,28 @@ LLVM_LIBC_FUNCTION(struct passwd *, getpwent, ()) {
     pwd_file = result.value();
   }
 
-  while (read_line(pwd_file, line_buffer, sizeof(line_buffer))) {
+  while (true) {
+    auto result = read_line(pwd_file, line_buffer, sizeof(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)
+      continue;
+
     // Remove newline
-    size_t len = LIBC_NAMESPACE::internal::string_length(line_buffer);
+    size_t len = res.bytes_read;
     if (len > 0 && line_buffer[len - 1] == '\n')
       line_buffer[len - 1] = '\0';
 
     if (internal::parse_passwd_line(line_buffer, &pwd_entry))
       return &pwd_entry;
   }
-
-  return nullptr;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
index 6a993c1129c5a..0512f874b0ad9 100644
--- a/libc/src/pwd/pwd_utils.cpp
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -39,18 +39,20 @@ bool parse_passwd_line(char *line, struct passwd *pwd) {
   if (!uid_str)
     return false;
   auto uid_res = LIBC_NAMESPACE::internal::strtointeger<uid_t>(uid_str, 10);
-  if (uid_res.has_error())
+  if (uid_res.has_error() || uid_res.parsed_len == 0 ||
+      uid_str[uid_res.parsed_len] != '\0')
     return false;
-  pwd->pw_uid = uid_res;
+  pwd->pw_uid = uid_res.value;
 
   char *gid_str =
       LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
   if (!gid_str)
     return false;
   auto gid_res = LIBC_NAMESPACE::internal::strtointeger<gid_t>(gid_str, 10);
-  if (gid_res.has_error())
+  if (gid_res.has_error() || gid_res.parsed_len == 0 ||
+      gid_str[gid_res.parsed_len] != '\0')
     return false;
-  pwd->pw_gid = gid_res;
+  pwd->pw_gid = gid_res.value;
 
   pwd->pw_gecos =
       LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
@@ -62,7 +64,6 @@ bool parse_passwd_line(char *line, struct passwd *pwd) {
   if (!pwd->pw_dir)
     return false;
 
-  // shell
   pwd->pw_shell =
       LIBC_NAMESPACE::internal::string_token<false>(nullptr, ":", &context);
   if (!pwd->pw_shell)
diff --git a/libc/src/pwd/pwd_utils.h b/libc/src/pwd/pwd_utils.h
index 2ff5b3211dbb6..c7510e17f8506 100644
--- a/libc/src/pwd/pwd_utils.h
+++ b/libc/src/pwd/pwd_utils.h
@@ -21,11 +21,12 @@ namespace LIBC_NAMESPACE_DECL {
 namespace internal {
 
 bool parse_passwd_line(char *line, struct passwd *pwd);
+void set_passwd_path(const char *path);
 
 } // namespace internal
 
-void setpwent_impl();
-void endpwent_impl();
+int setpwent_impl();
+int endpwent_impl();
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/pwd/setpwent.cpp b/libc/src/pwd/setpwent.cpp
index a4efcdacba2dc..077bccf8087d1 100644
--- a/libc/src/pwd/setpwent.cpp
+++ b/libc/src/pwd/setpwent.cpp
@@ -13,11 +13,16 @@
 
 #include "src/pwd/setpwent.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include "src/pwd/pwd_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-LLVM_LIBC_FUNCTION(void, setpwent, ()) { setpwent_impl(); }
+LLVM_LIBC_FUNCTION(void, setpwent, ()) {
+  int result = setpwent_impl();
+  if (result != 0)
+    libc_errno = result;
+}
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/pwd/CMakeLists.txt b/libc/test/src/pwd/CMakeLists.txt
index b5bf000443b38..f21b8d3551189 100644
--- a/libc/test/src/pwd/CMakeLists.txt
+++ b/libc/test/src/pwd/CMakeLists.txt
@@ -15,8 +15,24 @@ add_libc_unittest(
   SRCS
     getpwent_test.cpp
   DEPENDS
+    libc.include.pwd
+    libc.hdr.types.struct_passwd
     libc.src.pwd.getpwent
     libc.src.pwd.setpwent
     libc.src.pwd.endpwent
+    libc.src.pwd.pwd_utils
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
     libc.src.string.strcmp
 )
+
+add_libc_unittest(
+  pwd_utils_test
+  SUITE
+    libc_pwd_unittests
+  SRCS
+    pwd_utils_test.cpp
+  DEPENDS
+    libc.hdr.types.struct_passwd
+    libc.src.pwd.pwd_utils
+)
diff --git a/libc/test/src/pwd/getpwent_test.cpp b/libc/test/src/pwd/getpwent_test.cpp
index eabc09e502f69..8db17230fee30 100644
--- a/libc/test/src/pwd/getpwent_test.cpp
+++ b/libc/test/src/pwd/getpwent_test.cpp
@@ -11,56 +11,146 @@
 ///
 //===----------------------------------------------------------------------===//
 
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/File/file.h"
 #include "src/pwd/endpwent.h"
 #include "src/pwd/getpwent.h"
+#include "src/pwd/pwd_utils.h"
 #include "src/pwd/setpwent.h"
-#include "src/string/strcmp.h"
+#include "test/UnitTest/LibcTest.h"
 #include "test/UnitTest/Test.h"
 
-TEST(LlvmLibcPwdTest, GetPwentTest) {
-  // We assume /etc/passwd exists and contains at least "root" and "daemon".
-  // We also assume it does NOT contain "baduser".
+static const char *test_passwd_data =
+    "root:x:0:0:root:/root:/bin/bash\n"
+    "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n"
+    "baduser:x:invalid:invalid:::\n" // Should be skipped due to parse error
+    "user2:x:3:3:user2:/home/user2:/bin/sh\n";
 
-  bool found_root = false;
-  bool found_daemon = false;
-  bool found_baduser = false;
+static bool create_test_file(const char *path, const char *data) {
+  auto result = LIBC_NAMESPACE::openfile(path, "w");
+  if (!result.has_value())
+    return false;
+  auto f = result.value();
+  size_t len = 0;
+  while (data[len] != '\0')
+    len++;
+  auto write_result = f->write(data, len);
+  if (write_result.value != len) {
+    f->close();
+    return false;
+  }
+  f->close();
+  return true;
+}
 
-  LIBC_NAMESPACE::setpwent();
+TEST(LlvmLibcPwdTest, GetPwentTestHermetic) {
+  auto TEST_FILE = libc_make_test_file_path("getpwent_hermetic.test");
+  ASSERT_TRUE(create_test_file(TEST_FILE, test_passwd_data));
 
-  struct passwd *pw;
-  while ((pw = LIBC_NAMESPACE::getpwent()) != nullptr) {
-    if (LIBC_NAMESPACE::strcmp(pw->pw_name, "root") == 0) {
-      found_root = true;
-      ASSERT_EQ(pw->pw_uid, static_cast<uid_t>(0));
-    }
-    if (LIBC_NAMESPACE::strcmp(pw->pw_name, "daemon") == 0) {
-      found_daemon = true;
-    }
-    if (LIBC_NAMESPACE::strcmp(pw->pw_name, "baduser") == 0) {
-      found_baduser = true;
-    }
-  }
+  LIBC_NAMESPACE::internal::set_passwd_path(TEST_FILE);
+
+  // First entry: root
+  struct passwd *pw = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw != nullptr);
+  EXPECT_STREQ(pw->pw_name, "root");
+  EXPECT_STREQ(pw->pw_passwd, "x");
+  EXPECT_EQ(pw->pw_uid, 0u);
+  EXPECT_EQ(pw->pw_gid, 0u);
+  EXPECT_STREQ(pw->pw_gecos, "root");
+  EXPECT_STREQ(pw->pw_dir, "/root");
+  EXPECT_STREQ(pw->pw_shell, "/bin/bash");
+
+  // Second entry: daemon (skip baduser)
+  pw = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw != nullptr);
+  EXPECT_STREQ(pw->pw_name, "daemon");
+  EXPECT_EQ(pw->pw_uid, 1u);
+
+  // Third entry: user2 (skip baduser because of parse error)
+  pw = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw != nullptr);
+  EXPECT_STREQ(pw->pw_name, "user2");
+  EXPECT_EQ(pw->pw_uid, 3u);
 
-  ASSERT_TRUE(found_root);
-  ASSERT_TRUE(found_daemon);
-  ASSERT_FALSE(found_baduser);
+  // End of file
+  pw = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw == nullptr);
 
   LIBC_NAMESPACE::endpwent();
 }
 
-TEST(LlvmLibcPwdTest, SetPwentTest) {
+TEST(LlvmLibcPwdTest, SetPwentTestHermetic) {
+  auto TEST_FILE = libc_make_test_file_path("setpwent_hermetic.test");
+  ASSERT_TRUE(create_test_file(TEST_FILE, test_passwd_data));
+
+  LIBC_NAMESPACE::internal::set_passwd_path(TEST_FILE);
+
   // Read first entry
   LIBC_NAMESPACE::setpwent();
   struct passwd *pw1 = LIBC_NAMESPACE::getpwent();
   ASSERT_TRUE(pw1 != nullptr);
 
-  // Rewind
-  LIBC_NAMESPACE::setpwent();
+  // Copy pw1->pw_name
+  char pw1_name[256];
+  size_t i = 0;
+  for (; pw1->pw_name[i] != '\0' && i < sizeof(pw1_name) - 1; ++i) {
+    pw1_name[i] = pw1->pw_name[i];
+  }
+  pw1_name[i] = '\0';
+
+  // Read second entry
   struct passwd *pw2 = LIBC_NAMESPACE::getpwent();
   ASSERT_TRUE(pw2 != nullptr);
+  EXPECT_STRNE(pw1_name, pw2->pw_name); // Should be different
+
+  // Rewind
+  LIBC_NAMESPACE::setpwent();
+  struct passwd *pw3 = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw3 != nullptr);
+  EXPECT_STREQ(pw1_name, pw3->pw_name); // Should be first entry again
+
+  LIBC_NAMESPACE::endpwent();
+}
+
+TEST(LlvmLibcPwdTest, GetPwentTruncationTest) {
+  auto TEST_FILE = libc_make_test_file_path("getpwent_truncation.test");
+
+  char test_data[2048];
+  size_t idx = 0;
+
+  // "longuser:x:4:4:"
+  const char *prefix = "longuser:x:4:4:";
+  for (size_t i = 0; prefix[i] != '\0'; ++i)
+    test_data[idx++] = prefix[i];
+
+  // 1100 'a's (gecos field) - triggers truncation (>1023 chars)
+  for (int i = 0; i < 1100; ++i)
+    test_data[idx++] = 'a';
+
+  // ":/home/longuser:/bin/sh\n"
+  const char *suffix = ":/home/longuser:/bin/sh\n";
+  for (size_t i = 0; suffix[i] != '\0'; ++i)
+    test_data[idx++] = suffix[i];
+
+  // "nextuser:x:5:5:next:/home/next:/bin/sh\n"
+  const char *next = "nextuser:x:5:5:next:/home/next:/bin/sh\n";
+  for (size_t i = 0; next[i] != '\0'; ++i)
+    test_data[idx++] = next[i];
+
+  test_data[idx] = '\0';
+
+  ASSERT_TRUE(create_test_file(TEST_FILE, test_data));
+  LIBC_NAMESPACE::internal::set_passwd_path(TEST_FILE);
+
+  // The long line should be truncated, discarded, and skipped.
+  // We should directly get "nextuser".
+  struct passwd *pw = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw != nullptr);
+  EXPECT_STREQ(pw->pw_name, "nextuser");
+  EXPECT_EQ(pw->pw_uid, 5u);
 
-  // Should be the same entry
-  ASSERT_STREQ(pw1->pw_name, pw2->pw_name);
+  pw = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pw == nullptr);
 
   LIBC_NAMESPACE::endpwent();
 }
diff --git a/libc/test/src/pwd/pwd_utils_test.cpp b/libc/test/src/pwd/pwd_utils_test.cpp
new file mode 100644
index 0000000000000..f8e07cec83c95
--- /dev/null
+++ b/libc/test/src/pwd/pwd_utils_test.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_passwd_line.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/struct_passwd.h"
+#include "src/pwd/pwd_utils.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::internal::parse_passwd_line;
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_Success) {
+  char line[] = "root:x:0:0:root:/root:/bin/bash";
+  struct passwd pwd;
+  ASSERT_TRUE(parse_passwd_line(line, &pwd));
+  ASSERT_STREQ(pwd.pw_name, "root");
+  ASSERT_STREQ(pwd.pw_passwd, "x");
+  ASSERT_EQ(pwd.pw_uid, 0u);
+  ASSERT_EQ(pwd.pw_gid, 0u);
+  ASSERT_STREQ(pwd.pw_gecos, "root");
+  ASSERT_STREQ(pwd.pw_dir, "/root");
+  ASSERT_STREQ(pwd.pw_shell, "/bin/bash");
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_EmptyFields) {
+  char line[] = "root::0:0::/root:";
+  struct passwd pwd;
+  ASSERT_TRUE(parse_passwd_line(line, &pwd));
+  ASSERT_STREQ(pwd.pw_name, "root");
+  ASSERT_STREQ(pwd.pw_passwd, "");
+  ASSERT_EQ(pwd.pw_uid, 0u);
+  ASSERT_EQ(pwd.pw_gid, 0u);
+  ASSERT_STREQ(pwd.pw_gecos, "");
+  ASSERT_STREQ(pwd.pw_dir, "/root");
+  ASSERT_STREQ(pwd.pw_shell, "");
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_InvalidNumeric) {
+  char line1[] = "root:x:abc:0:root:/root:/bin/bash";
+  struct passwd pwd;
+  ASSERT_FALSE(parse_passwd_line(line1, &pwd));
+
+  char line2[] = "root:x:0:def:root:/root:/bin/bash";
+  ASSERT_FALSE(parse_passwd_line(line2, &pwd));
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_MissingFields) {
+  char line[] = "root:x:0:0:root:/root"; // Only 6 fields
+  struct passwd pwd;
+  ASSERT_FALSE(parse_passwd_line(line, &pwd));
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_NullInput) {
+  struct passwd pwd;
+  ASSERT_FALSE(parse_passwd_line(nullptr, &pwd));
+  ASSERT_FALSE(parse_passwd_line(nullptr, nullptr));
+}



More information about the libc-commits mailing list