[libc-commits] [libc] [libc] Add getpwnam_r and getpwuid_r entrypoints (PR #220833)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Fri Sep 4 00:17:49 PDT 2026


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

>From fef289d24ec9e2e3431199e3253309fac3d7327b Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Thu, 3 Sep 2026 21:28:22 +0100
Subject: [PATCH 1/2] [libc] Add getpwnam_r and getpwuid_r entrypoints
 (#220833)

Add reentrant getpwnam_r and getpwuid_r entrypoints using the
FlatFileDatabase lookup engine.

* Implement getpwnam_r and getpwuid_r entrypoints with crash-on-null
  validation and ERANGE reporting.
* Add find_by_name and find_by_uid lookups under namespace passwd using
  stack-local ScopedFlatFileDatabase streams for thread isolation.
* Add ScopedPasswdFile RAII test helper and LlvmLibcPwdTest fixture in
  pwd_test_utils.h in the global namespace, matching the convention of
  other test fixtures across libc and avoiding polluting the production
  pwd library namespace.
* Update FlatFileDatabase to gracefully skip blank lines during lookups.
* Register entrypoints in config/linux/*/entrypoints.txt and
  include/pwd.yaml.
* Add comprehensive unit tests in libc/test/src/pwd/ for name and uid
  lookups, buffer boundary conditions, and blank line handling.

Assisted-by: Automated tooling, human reviewed.
---
 libc/config/linux/aarch64/entrypoints.txt |   2 +
 libc/config/linux/arm/entrypoints.txt     |   2 +
 libc/config/linux/i386/entrypoints.txt    |   2 +
 libc/config/linux/riscv/entrypoints.txt   |   2 +
 libc/config/linux/x86_64/entrypoints.txt  |   2 +
 libc/include/pwd.yaml                     |  20 +++
 libc/src/pwd/CMakeLists.txt               |  34 +++++
 libc/src/pwd/flat_file_db.h               |  50 +++++---
 libc/src/pwd/getpwnam_r.cpp               |  43 +++++++
 libc/src/pwd/getpwnam_r.h                 |  29 +++++
 libc/src/pwd/getpwuid_r.cpp               |  42 +++++++
 libc/src/pwd/getpwuid_r.h                 |  30 +++++
 libc/src/pwd/pwd_utils.cpp                |  35 +++++-
 libc/src/pwd/pwd_utils.h                  |  16 +++
 libc/test/src/pwd/CMakeLists.txt          |  46 +++++++
 libc/test/src/pwd/flat_file_db_test.cpp   |  41 +++++++
 libc/test/src/pwd/getpwent_test.cpp       |  77 +++++-------
 libc/test/src/pwd/getpwnam_r_test.cpp     | 143 ++++++++++++++++++++++
 libc/test/src/pwd/getpwuid_r_test.cpp     | 140 +++++++++++++++++++++
 libc/test/src/pwd/pwd_test_utils.h        |  61 +++++++++
 20 files changed, 755 insertions(+), 62 deletions(-)
 create mode 100644 libc/src/pwd/getpwnam_r.cpp
 create mode 100644 libc/src/pwd/getpwnam_r.h
 create mode 100644 libc/src/pwd/getpwuid_r.cpp
 create mode 100644 libc/src/pwd/getpwuid_r.h
 create mode 100644 libc/test/src/pwd/getpwnam_r_test.cpp
 create mode 100644 libc/test/src/pwd/getpwuid_r_test.cpp
 create mode 100644 libc/test/src/pwd/pwd_test_utils.h

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 9340f49ed6bc7..5f913254324f5 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -48,6 +48,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # pwd.h entrypoints
     libc.src.pwd.endpwent
     libc.src.pwd.getpwent
+    libc.src.pwd.getpwnam_r
+    libc.src.pwd.getpwuid_r
     libc.src.pwd.setpwent
 
     # sched.h entrypoints
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index 4755425aba233..3bfec48b3afd9 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -36,6 +36,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # pwd.h entrypoints
     libc.src.pwd.endpwent
     libc.src.pwd.getpwent
+    libc.src.pwd.getpwnam_r
+    libc.src.pwd.getpwuid_r
     libc.src.pwd.setpwent
 
     # string.h entrypoints
diff --git a/libc/config/linux/i386/entrypoints.txt b/libc/config/linux/i386/entrypoints.txt
index 3942289a2e5b2..1b84b0e7a0519 100644
--- a/libc/config/linux/i386/entrypoints.txt
+++ b/libc/config/linux/i386/entrypoints.txt
@@ -5,6 +5,8 @@ set(TARGET_LIBC_ENTRYPOINTS
   # pwd.h entrypoints
   libc.src.pwd.endpwent
   libc.src.pwd.getpwent
+  libc.src.pwd.getpwnam_r
+  libc.src.pwd.getpwuid_r
   libc.src.pwd.setpwent
 )
 
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 82b125c561766..5c94d9ae04272 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -67,6 +67,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # pwd.h entrypoints
     libc.src.pwd.endpwent
     libc.src.pwd.getpwent
+    libc.src.pwd.getpwnam_r
+    libc.src.pwd.getpwuid_r
     libc.src.pwd.setpwent
 
     # sched.h entrypoints
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 85b8dffbd828d..e1fd015a99668 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -67,6 +67,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     # pwd.h entrypoints
     libc.src.pwd.endpwent
     libc.src.pwd.getpwent
+    libc.src.pwd.getpwnam_r
+    libc.src.pwd.getpwuid_r
     libc.src.pwd.setpwent
 
     # sched.h entrypoints
diff --git a/libc/include/pwd.yaml b/libc/include/pwd.yaml
index 40db5690e98b9..ef3656f3a1042 100644
--- a/libc/include/pwd.yaml
+++ b/libc/include/pwd.yaml
@@ -24,3 +24,23 @@ functions:
       - posix
     return_type: void
     arguments: []
+  - name: getpwnam_r
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: const char *
+      - type: struct passwd *
+      - type: char *
+      - type: size_t
+      - type: struct passwd **
+  - name: getpwuid_r
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: uid_t
+      - type: struct passwd *
+      - type: char *
+      - type: size_t
+      - type: struct passwd **
diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
index 46aa2cb18d2d0..0a5aaca358cd0 100644
--- a/libc/src/pwd/CMakeLists.txt
+++ b/libc/src/pwd/CMakeLists.txt
@@ -42,6 +42,40 @@ add_entrypoint_object(
     .pwd_utils
 )
 
+add_entrypoint_object(
+  getpwnam_r
+  SRCS
+    getpwnam_r.cpp
+  HDRS
+    getpwnam_r.h
+  DEPENDS
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_passwd
+    libc.src.__support.CPP.span
+    libc.src.__support.CPP.string_view
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    .pwd_utils
+)
+
+add_entrypoint_object(
+  getpwuid_r
+  SRCS
+    getpwuid_r.cpp
+  HDRS
+    getpwuid_r.h
+  DEPENDS
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_passwd
+    libc.hdr.types.uid_t
+    libc.src.__support.CPP.span
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    .pwd_utils
+)
+
 add_object_library(
   pwd_utils
   HDRS
diff --git a/libc/src/pwd/flat_file_db.h b/libc/src/pwd/flat_file_db.h
index 36e5c7c9fb0f2..f38a122c6bb09 100644
--- a/libc/src/pwd/flat_file_db.h
+++ b/libc/src/pwd/flat_file_db.h
@@ -30,6 +30,7 @@ namespace pwd {
 struct ReadLineResult {
   size_t bytes_read;
   bool truncated;
+  bool eof;
 };
 
 // Forward declaration of record parser for flat database files.
@@ -52,8 +53,10 @@ template <typename EntryType> class FlatFileDatabase {
   // fixed, bounded buffer without dynamic heap allocations or realloc.
   LIBC_INLINE static ErrorOr<ReadLineResult> read_line(File *f,
                                                        cpp::span<char> buf) {
-    if (!f || buf.size() < 2)
+    if (!f)
       return Error(EINVAL);
+    if (buf.size() < 2)
+      return Error(ERANGE);
 
     File::FileLock lock(f);
     size_t bytes_read = 0;
@@ -71,6 +74,8 @@ template <typename EntryType> class FlatFileDatabase {
         break;
     }
 
+    bool eof = (bytes_read == 0);
+
     auto read_span = buf.first(bytes_read);
     if (result.value == 1 && !read_span.empty() && read_span.back() != '\n') {
       truncated = true;
@@ -92,7 +97,7 @@ template <typename EntryType> class FlatFileDatabase {
       --bytes_read;
 
     buf[bytes_read] = '\0';
-    return ReadLineResult{bytes_read, truncated};
+    return ReadLineResult{bytes_read, truncated, eof};
   }
 
 public:
@@ -137,7 +142,8 @@ template <typename EntryType> class FlatFileDatabase {
   }
 
   // Reads and parses the next record from the database. Returns true if an
-  // entry was read, false if EOF was reached, or an Error on failure.
+  // entry was read, false if EOF was reached, or an Error on failure. Blank
+  // lines are skipped.
   LIBC_INLINE ErrorOr<bool> getnext(EntryType *entry, cpp::span<char> buffer) {
     if (!entry)
       return Error(EINVAL);
@@ -148,21 +154,27 @@ template <typename EntryType> class FlatFileDatabase {
         return Error(res.error());
     }
 
-    auto result = read_line(file, buffer);
-    if (!result.has_value())
-      return Error(result.error());
+    while (true) {
+      auto result = read_line(file, buffer);
+      if (!result.has_value())
+        return Error(result.error());
 
-    ReadLineResult res = result.value();
-    if (res.bytes_read == 0)
-      return false; // EOF
+      ReadLineResult res = result.value();
+      if (res.eof)
+        return false; // EOF
 
-    if (res.truncated)
-      return Error(ERANGE);
+      // Skip blank lines.
+      if (res.bytes_read == 0)
+        continue;
 
-    if (parse_line(buffer.first(res.bytes_read + 1), entry))
-      return true;
+      if (res.truncated)
+        return Error(ERANGE);
 
-    return Error(EINVAL);
+      if (parse_line(buffer.first(res.bytes_read + 1), entry))
+        return true;
+
+      return Error(EINVAL);
+    }
   }
 
   // Searches for a record matching a given predicate. Returns true if the
@@ -188,6 +200,16 @@ template <typename EntryType> class FlatFileDatabase {
   }
 };
 
+// RAII wrapper around FlatFileDatabase for stack-local database operations.
+// Automatically closes the database file stream on destruction.
+template <typename EntryType>
+class ScopedFlatFileDatabase : public FlatFileDatabase<EntryType> {
+public:
+  using FlatFileDatabase<EntryType>::FlatFileDatabase;
+
+  LIBC_INLINE ~ScopedFlatFileDatabase() { this->enddb(); }
+};
+
 } // namespace pwd
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/pwd/getpwnam_r.cpp b/libc/src/pwd/getpwnam_r.cpp
new file mode 100644
index 0000000000000..0d523a4d4b513
--- /dev/null
+++ b/libc/src/pwd/getpwnam_r.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwnam_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/getpwnam_r.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/null_check.h"
+#include "src/pwd/pwd_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, getpwnam_r,
+                   (const char *name, struct passwd *pwd, char *buffer,
+                    size_t bufsize, struct passwd **result)) {
+  LIBC_CRASH_ON_NULLPTR(name);
+  LIBC_CRASH_ON_NULLPTR(pwd);
+  LIBC_CRASH_ON_NULLPTR(buffer);
+  LIBC_CRASH_ON_NULLPTR(result);
+
+  *result = nullptr;
+
+  auto res = passwd::find_by_name(name, pwd, cpp::span<char>(buffer, bufsize));
+  if (!res.has_value())
+    return res.error();
+
+  *result = res.value() ? pwd : nullptr;
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/getpwnam_r.h b/libc/src/pwd/getpwnam_r.h
new file mode 100644
index 0000000000000..74f7e3f9222ca
--- /dev/null
+++ b/libc/src/pwd/getpwnam_r.h
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Header file for getpwnam_r function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_GETPWNAM_R_H
+#define LLVM_LIBC_SRC_PWD_GETPWNAM_R_H
+
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_passwd.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Searches the password database for an entry with the matching username.
+int getpwnam_r(const char *name, struct passwd *pwd, char *buffer,
+               size_t bufsize, struct passwd **result);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_GETPWNAM_R_H
diff --git a/libc/src/pwd/getpwuid_r.cpp b/libc/src/pwd/getpwuid_r.cpp
new file mode 100644
index 0000000000000..8ac018262e135
--- /dev/null
+++ b/libc/src/pwd/getpwuid_r.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of getpwuid_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pwd/getpwuid_r.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_passwd.h"
+#include "hdr/types/uid_t.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/null_check.h"
+#include "src/pwd/pwd_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, getpwuid_r,
+                   (uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize,
+                    struct passwd **result)) {
+  LIBC_CRASH_ON_NULLPTR(pwd);
+  LIBC_CRASH_ON_NULLPTR(buffer);
+  LIBC_CRASH_ON_NULLPTR(result);
+
+  *result = nullptr;
+
+  auto res = passwd::find_by_uid(uid, pwd, cpp::span<char>(buffer, bufsize));
+  if (!res.has_value())
+    return res.error();
+
+  *result = res.value() ? pwd : nullptr;
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/getpwuid_r.h b/libc/src/pwd/getpwuid_r.h
new file mode 100644
index 0000000000000..b8ac51da566a9
--- /dev/null
+++ b/libc/src/pwd/getpwuid_r.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
+/// Header file for getpwuid_r function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PWD_GETPWUID_R_H
+#define LLVM_LIBC_SRC_PWD_GETPWUID_R_H
+
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_passwd.h"
+#include "hdr/types/uid_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Searches the password database for an entry with the matching user ID.
+int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize,
+               struct passwd **result);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_GETPWUID_R_H
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
index e85ced60620bd..68d920341cf2b 100644
--- a/libc/src/pwd/pwd_utils.cpp
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -15,6 +15,7 @@
 #include "hdr/errno_macros.h"
 #include "hdr/types/struct_passwd.h"
 #include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
 #include "src/__support/macros/attributes.h"
 #include "src/pwd/flat_file_db.h"
 #include "src/string/string_utils.h"
@@ -42,6 +43,10 @@ ErrorOr<struct passwd> parse_passwd_line(char *line) {
 
 namespace passwd {
 
+// Exposed via TESTONLY_set_passwd_path for unit testing to direct operations
+// to hermetic temporary files.
+static const char *passwd_file_path = LIBC_COPT_PWD_FILE_PATH;
+
 static LIBC_CONSTINIT pwd::FlatFileDatabase<struct passwd>
     db(LIBC_COPT_PWD_FILE_PATH);
 // Note: These static buffers are process-global and NOT protected by a mutex
@@ -49,7 +54,15 @@ static LIBC_CONSTINIT pwd::FlatFileDatabase<struct passwd>
 static char line_buffer[1024];
 static struct passwd pwd_entry;
 
-void TESTONLY_set_passwd_path(const char *path) { db.set_path(path); }
+void TESTONLY_set_passwd_path(const char *path) {
+  passwd_file_path = path;
+  db.set_path(path);
+}
+
+void TESTONLY_reset_passwd_path() {
+  passwd_file_path = LIBC_COPT_PWD_FILE_PATH;
+  db.set_path(LIBC_COPT_PWD_FILE_PATH);
+}
 
 ErrorOr<void> open() { return db.setdb(); }
 
@@ -64,5 +77,25 @@ ErrorOr<struct passwd *> read_next() {
   return &pwd_entry;
 }
 
+ErrorOr<bool> find_by_name(cpp::string_view name, struct passwd *pwd,
+                           cpp::span<char> buffer, const char *path) {
+  pwd::ScopedFlatFileDatabase<struct passwd> local_db(path ? path
+                                                           : passwd_file_path);
+  auto matcher = [name](const struct passwd &entry) {
+    return cpp::string_view(entry.pw_name) == name;
+  };
+  return local_db.lookup(matcher, pwd, buffer);
+}
+
+ErrorOr<bool> find_by_uid(uid_t uid, struct passwd *pwd, cpp::span<char> buffer,
+                          const char *path) {
+  pwd::ScopedFlatFileDatabase<struct passwd> local_db(path ? path
+                                                           : passwd_file_path);
+  auto matcher = [uid](const struct passwd &entry) {
+    return entry.pw_uid == uid;
+  };
+  return local_db.lookup(matcher, pwd, buffer);
+}
+
 } // namespace passwd
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/pwd_utils.h b/libc/src/pwd/pwd_utils.h
index 4354f360e692c..46a65e4dc5565 100644
--- a/libc/src/pwd/pwd_utils.h
+++ b/libc/src/pwd/pwd_utils.h
@@ -19,6 +19,7 @@
 #include "hdr/types/struct_passwd.h"
 #include "hdr/types/uid_t.h"
 #include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/attributes.h"
@@ -98,6 +99,9 @@ namespace passwd {
 // Overrides the default password file path for testing purposes.
 void TESTONLY_set_passwd_path(const char *path);
 
+// Resets the password file path back to the default.
+void TESTONLY_reset_passwd_path();
+
 // Opens or rewinds the password file.
 ErrorOr<void> open();
 
@@ -107,6 +111,18 @@ ErrorOr<void> close();
 // Reads the next entry from the password database.
 ErrorOr<struct passwd *> read_next();
 
+// Searches for a password entry matching the given username.
+// The optional path parameter allows unit tests to direct lookups to hermetic
+// test database files without mutating global state.
+ErrorOr<bool> find_by_name(cpp::string_view name, struct passwd *pwd,
+                           cpp::span<char> buffer, const char *path = nullptr);
+
+// Searches for a password entry matching the given user ID.
+// The optional path parameter allows unit tests to direct lookups to hermetic
+// test database files without mutating global state.
+ErrorOr<bool> find_by_uid(uid_t uid, struct passwd *pwd, cpp::span<char> buffer,
+                          const char *path = nullptr);
+
 } // namespace passwd
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/src/pwd/CMakeLists.txt b/libc/test/src/pwd/CMakeLists.txt
index fb1e6e8ee5fce..fe063ebdaf068 100644
--- a/libc/test/src/pwd/CMakeLists.txt
+++ b/libc/test/src/pwd/CMakeLists.txt
@@ -47,6 +47,8 @@ add_libc_test(
   getpwent_test
   SUITE
     libc_pwd_unittests
+  HDRS
+    pwd_test_utils.h
   SRCS
     getpwent_test.cpp
   DEPENDS
@@ -62,3 +64,47 @@ add_libc_test(
     libc.src.stdio.remove
     libc.src.string.string_utils
 )
+
+add_libc_test(
+  getpwnam_r_test
+  SUITE
+    libc_pwd_unittests
+  HDRS
+    pwd_test_utils.h
+  SRCS
+    getpwnam_r_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_passwd
+    libc.hdr.types.uid_t
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
+    libc.src.pwd.getpwnam_r
+    libc.src.pwd.pwd_utils
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+)
+
+add_libc_test(
+  getpwuid_r_test
+  SUITE
+    libc_pwd_unittests
+  HDRS
+    pwd_test_utils.h
+  SRCS
+    getpwuid_r_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.types.gid_t
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_passwd
+    libc.hdr.types.uid_t
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
+    libc.src.pwd.getpwuid_r
+    libc.src.pwd.pwd_utils
+    libc.src.stdio.remove
+    libc.src.string.string_utils
+)
diff --git a/libc/test/src/pwd/flat_file_db_test.cpp b/libc/test/src/pwd/flat_file_db_test.cpp
index 3f13d55cf4d34..94af32967c3cf 100644
--- a/libc/test/src/pwd/flat_file_db_test.cpp
+++ b/libc/test/src/pwd/flat_file_db_test.cpp
@@ -176,3 +176,44 @@ TEST_F(LlvmLibcFlatFileDbTest, MalformedLineReturnsEinval) {
 
   db.enddb();
 }
+
+TEST_F(LlvmLibcFlatFileDbTest, BlankLinesSkipped) {
+  const char *content = "\n\nuser1:secret1\n\n\nuser2:secret2\n\n";
+  HermeticFile test_file(libc_make_test_file_path("flat_db_blank.test"),
+                         content);
+
+  LIBC_NAMESPACE::pwd::ScopedFlatFileDatabase<SimpleTestEntry> db(
+      test_file.get_path());
+  char buffer[128];
+  SimpleTestEntry entry;
+
+  // First record (skipping leading blank lines)
+  auto r1 = db.getnext(&entry, buffer);
+  ASSERT_TRUE(r1.has_value());
+  ASSERT_TRUE(r1.value());
+  ASSERT_STREQ(entry.key, "user1");
+  ASSERT_STREQ(entry.val, "secret1");
+
+  // Second record (skipping consecutive blank lines)
+  auto r2 = db.getnext(&entry, buffer);
+  ASSERT_TRUE(r2.has_value());
+  ASSERT_TRUE(r2.value());
+  ASSERT_STREQ(entry.key, "user2");
+  ASSERT_STREQ(entry.val, "secret2");
+
+  // EOF (skipping trailing blank lines)
+  auto r3 = db.getnext(&entry, buffer);
+  ASSERT_TRUE(r3.has_value());
+  ASSERT_FALSE(r3.value());
+
+  // Rewind and lookup across blank lines
+  db.setdb();
+  auto matcher = [](const SimpleTestEntry &e) {
+    return LIBC_NAMESPACE::cpp::string_view(e.key) == "user2";
+  };
+  auto lookup_res = db.lookup(matcher, &entry, buffer);
+  ASSERT_TRUE(lookup_res.has_value());
+  ASSERT_TRUE(lookup_res.value());
+  ASSERT_STREQ(entry.key, "user2");
+  ASSERT_STREQ(entry.val, "secret2");
+}
diff --git a/libc/test/src/pwd/getpwent_test.cpp b/libc/test/src/pwd/getpwent_test.cpp
index b8dbd9a4cb161..20ff0bee6feee 100644
--- a/libc/test/src/pwd/getpwent_test.cpp
+++ b/libc/test/src/pwd/getpwent_test.cpp
@@ -13,56 +13,23 @@
 
 #include "hdr/errno_macros.h"
 #include "hdr/types/struct_passwd.h"
-#include "src/__support/File/file.h"
+#include "pwd_test_utils.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 "src/string/string_utils.h"
-#include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/ErrnoSetterMatcher.h"
 #include "test/UnitTest/Test.h"
 
 using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
 
-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) {
-    LIBC_NAMESPACE::internal::strlcpy(path, file_path, sizeof(path));
-
-    auto file_or = LIBC_NAMESPACE::openfile(path, "w");
-    if (file_or.has_value()) {
-      auto *f = file_or.value();
-      size_t len = LIBC_NAMESPACE::internal::string_length(content);
-      f->write(content, len);
-      f->close();
-    }
-  }
-
-  ~HermeticFile() { LIBC_NAMESPACE::remove(path); }
-
-  const char *get_path() const { return path; }
-};
-
-class LlvmLibcPwdTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {};
-
-} // namespace
-
 TEST_F(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);
+  ScopedPasswdFile test_file(libc_make_test_file_path("getpwent_success.test"),
+                             content);
 
-  LIBC_NAMESPACE::passwd::TESTONLY_set_passwd_path(test_file.get_path());
   LIBC_NAMESPACE::setpwent();
 
   struct passwd *pwd1 = LIBC_NAMESPACE::getpwent();
@@ -83,10 +50,9 @@ TEST_F(LlvmLibcPwdTest, GetPwentTestSuccess) {
 
 TEST_F(LlvmLibcPwdTest, GetPwentTestFailure) {
   const char *content = "invalid_line_without_enough_fields\n";
-  HermeticFile test_file(libc_make_test_file_path("getpwent_fail.test"),
-                         content);
+  ScopedPasswdFile test_file(libc_make_test_file_path("getpwent_fail.test"),
+                             content);
 
-  LIBC_NAMESPACE::passwd::TESTONLY_set_passwd_path(test_file.get_path());
   LIBC_NAMESPACE::setpwent();
 
   struct passwd *pwd = LIBC_NAMESPACE::getpwent();
@@ -99,10 +65,8 @@ TEST_F(LlvmLibcPwdTest, GetPwentTestFailure) {
 TEST_F(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::passwd::TESTONLY_set_passwd_path(test_file.get_path());
+  ScopedPasswdFile test_file(libc_make_test_file_path("setpwent_hermetic.test"),
+                             content);
 
   struct passwd *pwd = LIBC_NAMESPACE::getpwent();
   ASSERT_TRUE(pwd != nullptr);
@@ -124,10 +88,8 @@ TEST_F(LlvmLibcPwdTest, SetPwentTestHermetic) {
 
 TEST_F(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::passwd::TESTONLY_set_passwd_path(test_file.get_path());
+  ScopedPasswdFile test_file(libc_make_test_file_path("reopen_endpwent.test"),
+                             content);
 
   struct passwd *pwd = LIBC_NAMESPACE::getpwent();
   ASSERT_TRUE(pwd != nullptr);
@@ -151,3 +113,24 @@ TEST_F(LlvmLibcPwdTest, FileOpenFailure) {
   ASSERT_TRUE(pwd == nullptr);
   ASSERT_ERRNO_EQ(ENOENT);
 }
+
+TEST_F(LlvmLibcPwdTest, BlankLines) {
+  const char *content = "\nroot:x:0:0:root:/root:/bin/bash\n\n\n"
+                        "bin:x:1:1:bin:/bin:/sbin/nologin\n\n";
+  ScopedPasswdFile test_file(libc_make_test_file_path("getpwent_blank.test"),
+                             content);
+
+  LIBC_NAMESPACE::setpwent();
+  struct passwd *pwd1 = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pwd1 != nullptr);
+  ASSERT_STREQ(pwd1->pw_name, "root");
+
+  struct passwd *pwd2 = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pwd2 != nullptr);
+  ASSERT_STREQ(pwd2->pw_name, "bin");
+
+  struct passwd *pwd3 = LIBC_NAMESPACE::getpwent();
+  ASSERT_TRUE(pwd3 == nullptr);
+
+  LIBC_NAMESPACE::endpwent();
+}
diff --git a/libc/test/src/pwd/getpwnam_r_test.cpp b/libc/test/src/pwd/getpwnam_r_test.cpp
new file mode 100644
index 0000000000000..b9a45c22e7592
--- /dev/null
+++ b/libc/test/src/pwd/getpwnam_r_test.cpp
@@ -0,0 +1,143 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwnam_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_passwd.h"
+#include "hdr/types/uid_t.h"
+#include "pwd_test_utils.h"
+#include "src/pwd/getpwnam_r.h"
+#include "src/pwd/pwd_utils.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcGetpwnamRTest = LlvmLibcPwdTest;
+
+TEST_F(LlvmLibcGetpwnamRTest, Success) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n"
+                        "bin:x:1:1:bin:/bin:/sbin/nologin\n"
+                        "daemon:x:2:2:daemon:/sbin:/sbin/nologin\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwnam_r_success.test"), content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = nullptr;
+
+  int ret =
+      LIBC_NAMESPACE::getpwnam_r("bin", &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "bin");
+  ASSERT_STREQ(pwd.pw_passwd, "x");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
+  ASSERT_EQ(pwd.pw_gid, static_cast<gid_t>(1));
+  ASSERT_STREQ(pwd.pw_gecos, "bin");
+  ASSERT_STREQ(pwd.pw_dir, "/bin");
+  ASSERT_STREQ(pwd.pw_shell, "/sbin/nologin");
+}
+
+TEST_F(LlvmLibcGetpwnamRTest, FirstAndLastEntries) {
+  const char *content = "first:x:100:100:first:/home/first:/bin/sh\n"
+                        "middle:x:101:101:middle:/home/middle:/bin/sh\n"
+                        "last:x:102:102:last:/home/last:/bin/sh\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwnam_r_boundary.test"), content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = nullptr;
+
+  // Lookup the first entry
+  int ret = LIBC_NAMESPACE::getpwnam_r("first", &pwd, buffer, sizeof(buffer),
+                                       &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "first");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(100));
+
+  // Lookup the last entry
+  result = nullptr;
+  ret =
+      LIBC_NAMESPACE::getpwnam_r("last", &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "last");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(102));
+}
+
+TEST_F(LlvmLibcGetpwnamRTest, NotFound) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwnam_r_notfound.test"), content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+
+  int ret = LIBC_NAMESPACE::getpwnam_r("nonexistent", &pwd, buffer,
+                                       sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+}
+
+TEST_F(LlvmLibcGetpwnamRTest, BufferTooSmall) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwnam_r_toosmall.test"), content);
+
+  struct passwd pwd;
+  char small_buf[8];
+  struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+
+  int ret = LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf,
+                                       sizeof(small_buf), &result);
+  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+
+  // Single-byte buffer is insufficient and must return ERANGE.
+  char tiny_buf[1];
+  result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+  ret = LIBC_NAMESPACE::getpwnam_r("root", &pwd, tiny_buf, sizeof(tiny_buf),
+                                   &result);
+  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+
+  // Zero-byte buffer is insufficient and must return ERANGE.
+  result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+  ret = LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf, 0, &result);
+  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+
+  // Note: passing nullptr for name, pwd, buffer, or result is undefined
+  // behavior per POSIX. The implementation uses LIBC_CRASH_ON_NULLPTR for each
+  // pointer, so there are no nullptr tests here in hermetic unit tests.
+}
+
+TEST_F(LlvmLibcGetpwnamRTest, BlankLines) {
+  const char *content = "\nroot:x:0:0:root:/root:/bin/bash\n\n\n"
+                        "bin:x:1:1:bin:/bin:/sbin/nologin\n\n";
+  ScopedPasswdFile test_file(libc_make_test_file_path("getpwnam_r_blank.test"),
+                             content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = nullptr;
+
+  int ret =
+      LIBC_NAMESPACE::getpwnam_r("bin", &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "bin");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
+}
diff --git a/libc/test/src/pwd/getpwuid_r_test.cpp b/libc/test/src/pwd/getpwuid_r_test.cpp
new file mode 100644
index 0000000000000..c857acfc5766d
--- /dev/null
+++ b/libc/test/src/pwd/getpwuid_r_test.cpp
@@ -0,0 +1,140 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 getpwuid_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/gid_t.h"
+#include "hdr/types/size_t.h"
+#include "hdr/types/struct_passwd.h"
+#include "hdr/types/uid_t.h"
+#include "pwd_test_utils.h"
+#include "src/pwd/getpwuid_r.h"
+#include "src/pwd/pwd_utils.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcGetpwuidRTest = LlvmLibcPwdTest;
+
+TEST_F(LlvmLibcGetpwuidRTest, Success) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n"
+                        "bin:x:1:1:bin:/bin:/sbin/nologin\n"
+                        "daemon:x:2:2:daemon:/sbin:/sbin/nologin\n"
+                        "nobody:x:65534:65534:nobody:/nonexistent:/bin/false\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwuid_r_success.test"), content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = nullptr;
+
+  int ret =
+      LIBC_NAMESPACE::getpwuid_r(1, &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "bin");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
+  ASSERT_EQ(pwd.pw_gid, static_cast<gid_t>(1));
+  ASSERT_STREQ(pwd.pw_dir, "/bin");
+  ASSERT_STREQ(pwd.pw_shell, "/sbin/nologin");
+
+  // Lookup high UID (nobody)
+  result = nullptr;
+  ret =
+      LIBC_NAMESPACE::getpwuid_r(65534, &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "nobody");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(65534));
+}
+
+TEST_F(LlvmLibcGetpwuidRTest, RootUidZero) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n"
+                        "bin:x:1:1:bin:/bin:/sbin/nologin\n";
+  ScopedPasswdFile test_file(libc_make_test_file_path("getpwuid_r_zero.test"),
+                             content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = nullptr;
+
+  int ret =
+      LIBC_NAMESPACE::getpwuid_r(0, &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "root");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(0));
+}
+
+TEST_F(LlvmLibcGetpwuidRTest, NotFound) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwuid_r_notfound.test"), content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+
+  int ret =
+      LIBC_NAMESPACE::getpwuid_r(999, &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+}
+
+TEST_F(LlvmLibcGetpwuidRTest, BufferTooSmall) {
+  const char *content = "root:x:0:0:root:/root:/bin/bash\n";
+  ScopedPasswdFile test_file(
+      libc_make_test_file_path("getpwuid_r_toosmall.test"), content);
+
+  struct passwd pwd;
+  char small_buf[8];
+  struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+
+  int ret = LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, sizeof(small_buf),
+                                       &result);
+  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+
+  // Single-byte buffer is insufficient and must return ERANGE.
+  char tiny_buf[1];
+  result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+  ret =
+      LIBC_NAMESPACE::getpwuid_r(0, &pwd, tiny_buf, sizeof(tiny_buf), &result);
+  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+
+  // Zero-byte buffer is insufficient and must return ERANGE.
+  result = reinterpret_cast<struct passwd *>(0xdeadbeef);
+  ret = LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, 0, &result);
+  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
+
+  // Note: passing nullptr for pwd, buffer, or result is undefined behavior per
+  // POSIX. The implementation uses LIBC_CRASH_ON_NULLPTR for each pointer, so
+  // there are no nullptr tests here in hermetic unit tests.
+}
+
+TEST_F(LlvmLibcGetpwuidRTest, BlankLines) {
+  const char *content = "\nroot:x:0:0:root:/root:/bin/bash\n\n\n"
+                        "bin:x:1:1:bin:/bin:/sbin/nologin\n\n";
+  ScopedPasswdFile test_file(libc_make_test_file_path("getpwuid_r_blank.test"),
+                             content);
+
+  struct passwd pwd;
+  char buffer[256];
+  struct passwd *result = nullptr;
+
+  int ret =
+      LIBC_NAMESPACE::getpwuid_r(1, &pwd, buffer, sizeof(buffer), &result);
+  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(result, &pwd);
+  ASSERT_STREQ(pwd.pw_name, "bin");
+  ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
+}
diff --git a/libc/test/src/pwd/pwd_test_utils.h b/libc/test/src/pwd/pwd_test_utils.h
new file mode 100644
index 0000000000000..707ea857db0e3
--- /dev/null
+++ b/libc/test/src/pwd/pwd_test_utils.h
@@ -0,0 +1,61 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Shared test utilities and fixtures for pwd tests.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TEST_SRC_PWD_PWD_TEST_UTILS_H
+#define LLVM_LIBC_TEST_SRC_PWD_PWD_TEST_UTILS_H
+
+#include "hdr/types/size_t.h"
+#include "src/__support/File/file.h"
+#include "src/pwd/pwd_utils.h"
+#include "src/stdio/remove.h"
+#include "src/string/string_utils.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+// RAII helper class for creating and automatically removing temporary test
+// files, while safely scoping the password database path.
+class ScopedPasswdFile {
+  char path[256];
+
+public:
+  ScopedPasswdFile(const char *file_path, const char *content) {
+    LIBC_NAMESPACE::internal::strlcpy(path, file_path, sizeof(path));
+
+    auto file_or = LIBC_NAMESPACE::openfile(path, "w");
+    if (file_or.has_value()) {
+      auto *f = file_or.value();
+      size_t len = LIBC_NAMESPACE::internal::string_length(content);
+      f->write(content, len);
+      f->close();
+    }
+    LIBC_NAMESPACE::passwd::TESTONLY_set_passwd_path(path);
+  }
+
+  ~ScopedPasswdFile() {
+    LIBC_NAMESPACE::passwd::TESTONLY_reset_passwd_path();
+    LIBC_NAMESPACE::remove(path);
+  }
+
+  const char *get_path() const { return path; }
+};
+
+// Base test fixture that resets the password database path and validates errno.
+class LlvmLibcPwdTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+protected:
+  void TearDown() override {
+    LIBC_NAMESPACE::passwd::TESTONLY_reset_passwd_path();
+    ErrnoCheckingTest::TearDown();
+  }
+};
+
+#endif // LLVM_LIBC_TEST_SRC_PWD_PWD_TEST_UTILS_H

>From cd75193b894169fda797ac8dd7acaa7dcd700f7b Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 4 Sep 2026 08:16:24 +0100
Subject: [PATCH 2/2] [libc] Unify pwd namespace and simplify test assertions
 (#220833)

Consolidate internal password utilities into namespace pwd, eliminating
the separate namespace passwd.

Document ReadLineResult::eof semantics in FlatFileDatabase.

Inline getpwnam_r and getpwuid_r calls into ASSERT_EQ in unit tests to
eliminate the ret temporary variable.

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/pwd/endpwent.cpp             |  2 +-
 libc/src/pwd/flat_file_db.h           |  1 +
 libc/src/pwd/getpwent.cpp             |  2 +-
 libc/src/pwd/getpwnam_r.cpp           |  2 +-
 libc/src/pwd/getpwuid_r.cpp           |  2 +-
 libc/src/pwd/pwd_utils.cpp            | 16 ++++------
 libc/src/pwd/pwd_utils.h              |  6 +---
 libc/src/pwd/setpwent.cpp             |  2 +-
 libc/test/src/pwd/getpwent_test.cpp   |  2 +-
 libc/test/src/pwd/getpwnam_r_test.cpp | 46 +++++++++++++--------------
 libc/test/src/pwd/getpwuid_r_test.cpp | 42 +++++++++++-------------
 libc/test/src/pwd/pwd_test_utils.h    |  6 ++--
 12 files changed, 59 insertions(+), 70 deletions(-)

diff --git a/libc/src/pwd/endpwent.cpp b/libc/src/pwd/endpwent.cpp
index e5edde36d0d2d..189b503b6d402 100644
--- a/libc/src/pwd/endpwent.cpp
+++ b/libc/src/pwd/endpwent.cpp
@@ -19,7 +19,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(void, endpwent, ()) {
-  auto res = passwd::close();
+  auto res = pwd::close();
   if (!res.has_value())
     libc_errno = res.error();
 }
diff --git a/libc/src/pwd/flat_file_db.h b/libc/src/pwd/flat_file_db.h
index f38a122c6bb09..4d7bff816c57d 100644
--- a/libc/src/pwd/flat_file_db.h
+++ b/libc/src/pwd/flat_file_db.h
@@ -30,6 +30,7 @@ namespace pwd {
 struct ReadLineResult {
   size_t bytes_read;
   bool truncated;
+  // True only when no data was read because the file stream reached EOF.
   bool eof;
 };
 
diff --git a/libc/src/pwd/getpwent.cpp b/libc/src/pwd/getpwent.cpp
index ab0f8d05f2794..77ab0f7e6161b 100644
--- a/libc/src/pwd/getpwent.cpp
+++ b/libc/src/pwd/getpwent.cpp
@@ -19,7 +19,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(struct passwd *, getpwent, ()) {
-  auto res = passwd::read_next();
+  auto res = pwd::read_next();
   if (!res.has_value()) {
     libc_errno = res.error();
     return nullptr;
diff --git a/libc/src/pwd/getpwnam_r.cpp b/libc/src/pwd/getpwnam_r.cpp
index 0d523a4d4b513..6893cf769e3c1 100644
--- a/libc/src/pwd/getpwnam_r.cpp
+++ b/libc/src/pwd/getpwnam_r.cpp
@@ -32,7 +32,7 @@ LLVM_LIBC_FUNCTION(int, getpwnam_r,
 
   *result = nullptr;
 
-  auto res = passwd::find_by_name(name, pwd, cpp::span<char>(buffer, bufsize));
+  auto res = pwd::find_by_name(name, pwd, cpp::span<char>(buffer, bufsize));
   if (!res.has_value())
     return res.error();
 
diff --git a/libc/src/pwd/getpwuid_r.cpp b/libc/src/pwd/getpwuid_r.cpp
index 8ac018262e135..1be957fa966fb 100644
--- a/libc/src/pwd/getpwuid_r.cpp
+++ b/libc/src/pwd/getpwuid_r.cpp
@@ -31,7 +31,7 @@ LLVM_LIBC_FUNCTION(int, getpwuid_r,
 
   *result = nullptr;
 
-  auto res = passwd::find_by_uid(uid, pwd, cpp::span<char>(buffer, bufsize));
+  auto res = pwd::find_by_uid(uid, pwd, cpp::span<char>(buffer, bufsize));
   if (!res.has_value())
     return res.error();
 
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
index 68d920341cf2b..cc10a21bef021 100644
--- a/libc/src/pwd/pwd_utils.cpp
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -39,15 +39,11 @@ ErrorOr<struct passwd> parse_passwd_line(char *line) {
   return pwd;
 }
 
-} // namespace pwd
-
-namespace passwd {
-
 // Exposed via TESTONLY_set_passwd_path for unit testing to direct operations
 // to hermetic temporary files.
 static const char *passwd_file_path = LIBC_COPT_PWD_FILE_PATH;
 
-static LIBC_CONSTINIT pwd::FlatFileDatabase<struct passwd>
+static LIBC_CONSTINIT FlatFileDatabase<struct passwd>
     db(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.
@@ -79,8 +75,8 @@ ErrorOr<struct passwd *> read_next() {
 
 ErrorOr<bool> find_by_name(cpp::string_view name, struct passwd *pwd,
                            cpp::span<char> buffer, const char *path) {
-  pwd::ScopedFlatFileDatabase<struct passwd> local_db(path ? path
-                                                           : passwd_file_path);
+  ScopedFlatFileDatabase<struct passwd> local_db(path ? path
+                                                      : passwd_file_path);
   auto matcher = [name](const struct passwd &entry) {
     return cpp::string_view(entry.pw_name) == name;
   };
@@ -89,13 +85,13 @@ ErrorOr<bool> find_by_name(cpp::string_view name, struct passwd *pwd,
 
 ErrorOr<bool> find_by_uid(uid_t uid, struct passwd *pwd, cpp::span<char> buffer,
                           const char *path) {
-  pwd::ScopedFlatFileDatabase<struct passwd> local_db(path ? path
-                                                           : passwd_file_path);
+  ScopedFlatFileDatabase<struct passwd> local_db(path ? path
+                                                      : passwd_file_path);
   auto matcher = [uid](const struct passwd &entry) {
     return entry.pw_uid == uid;
   };
   return local_db.lookup(matcher, pwd, buffer);
 }
 
-} // namespace passwd
+} // namespace pwd
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pwd/pwd_utils.h b/libc/src/pwd/pwd_utils.h
index 46a65e4dc5565..249de89e258d2 100644
--- a/libc/src/pwd/pwd_utils.h
+++ b/libc/src/pwd/pwd_utils.h
@@ -92,10 +92,6 @@ LIBC_INLINE bool parse_line<struct passwd>(cpp::span<char> line,
 // Parses a colon-separated password database line into a struct passwd.
 ErrorOr<struct passwd> parse_passwd_line(char *line);
 
-} // namespace pwd
-
-namespace passwd {
-
 // Overrides the default password file path for testing purposes.
 void TESTONLY_set_passwd_path(const char *path);
 
@@ -123,7 +119,7 @@ ErrorOr<bool> find_by_name(cpp::string_view name, struct passwd *pwd,
 ErrorOr<bool> find_by_uid(uid_t uid, struct passwd *pwd, cpp::span<char> buffer,
                           const char *path = nullptr);
 
-} // namespace passwd
+} // namespace pwd
 } // 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
index effedd493caf7..19151328c8aa7 100644
--- a/libc/src/pwd/setpwent.cpp
+++ b/libc/src/pwd/setpwent.cpp
@@ -19,7 +19,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(void, setpwent, ()) {
-  auto res = passwd::open();
+  auto res = pwd::open();
   if (!res.has_value())
     libc_errno = res.error();
 }
diff --git a/libc/test/src/pwd/getpwent_test.cpp b/libc/test/src/pwd/getpwent_test.cpp
index 20ff0bee6feee..29313953535c6 100644
--- a/libc/test/src/pwd/getpwent_test.cpp
+++ b/libc/test/src/pwd/getpwent_test.cpp
@@ -105,7 +105,7 @@ TEST_F(LlvmLibcPwdTest, ReopenAfterEndpwent) {
 }
 
 TEST_F(LlvmLibcPwdTest, FileOpenFailure) {
-  LIBC_NAMESPACE::passwd::TESTONLY_set_passwd_path(
+  LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(
       "/nonexistent_directory/nonexistent_file");
   LIBC_NAMESPACE::endpwent(); // Force close any existing file
 
diff --git a/libc/test/src/pwd/getpwnam_r_test.cpp b/libc/test/src/pwd/getpwnam_r_test.cpp
index b9a45c22e7592..3f9dcfa747b8b 100644
--- a/libc/test/src/pwd/getpwnam_r_test.cpp
+++ b/libc/test/src/pwd/getpwnam_r_test.cpp
@@ -34,9 +34,9 @@ TEST_F(LlvmLibcGetpwnamRTest, Success) {
   char buffer[256];
   struct passwd *result = nullptr;
 
-  int ret =
-      LIBC_NAMESPACE::getpwnam_r("bin", &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwnam_r("bin", &pwd, buffer, sizeof(buffer), &result),
+      0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "bin");
   ASSERT_STREQ(pwd.pw_passwd, "x");
@@ -59,18 +59,18 @@ TEST_F(LlvmLibcGetpwnamRTest, FirstAndLastEntries) {
   struct passwd *result = nullptr;
 
   // Lookup the first entry
-  int ret = LIBC_NAMESPACE::getpwnam_r("first", &pwd, buffer, sizeof(buffer),
-                                       &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("first", &pwd, buffer, sizeof(buffer),
+                                       &result),
+            0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "first");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(100));
 
   // Lookup the last entry
   result = nullptr;
-  ret =
-      LIBC_NAMESPACE::getpwnam_r("last", &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwnam_r("last", &pwd, buffer, sizeof(buffer), &result),
+      0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "last");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(102));
@@ -85,9 +85,9 @@ TEST_F(LlvmLibcGetpwnamRTest, NotFound) {
   char buffer[256];
   struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
 
-  int ret = LIBC_NAMESPACE::getpwnam_r("nonexistent", &pwd, buffer,
-                                       sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("nonexistent", &pwd, buffer,
+                                       sizeof(buffer), &result),
+            0);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 }
 
@@ -100,23 +100,23 @@ TEST_F(LlvmLibcGetpwnamRTest, BufferTooSmall) {
   char small_buf[8];
   struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
 
-  int ret = LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf,
-                                       sizeof(small_buf), &result);
-  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf,
+                                       sizeof(small_buf), &result),
+            ERANGE);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 
   // Single-byte buffer is insufficient and must return ERANGE.
   char tiny_buf[1];
   result = reinterpret_cast<struct passwd *>(0xdeadbeef);
-  ret = LIBC_NAMESPACE::getpwnam_r("root", &pwd, tiny_buf, sizeof(tiny_buf),
-                                   &result);
-  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("root", &pwd, tiny_buf, sizeof(tiny_buf),
+                                       &result),
+            ERANGE);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 
   // Zero-byte buffer is insufficient and must return ERANGE.
   result = reinterpret_cast<struct passwd *>(0xdeadbeef);
-  ret = LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf, 0, &result);
-  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf, 0, &result),
+            ERANGE);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 
   // Note: passing nullptr for name, pwd, buffer, or result is undefined
@@ -134,9 +134,9 @@ TEST_F(LlvmLibcGetpwnamRTest, BlankLines) {
   char buffer[256];
   struct passwd *result = nullptr;
 
-  int ret =
-      LIBC_NAMESPACE::getpwnam_r("bin", &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwnam_r("bin", &pwd, buffer, sizeof(buffer), &result),
+      0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "bin");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
diff --git a/libc/test/src/pwd/getpwuid_r_test.cpp b/libc/test/src/pwd/getpwuid_r_test.cpp
index c857acfc5766d..39726f97710fd 100644
--- a/libc/test/src/pwd/getpwuid_r_test.cpp
+++ b/libc/test/src/pwd/getpwuid_r_test.cpp
@@ -35,9 +35,8 @@ TEST_F(LlvmLibcGetpwuidRTest, Success) {
   char buffer[256];
   struct passwd *result = nullptr;
 
-  int ret =
-      LIBC_NAMESPACE::getpwuid_r(1, &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwuid_r(1, &pwd, buffer, sizeof(buffer), &result), 0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "bin");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
@@ -47,9 +46,9 @@ TEST_F(LlvmLibcGetpwuidRTest, Success) {
 
   // Lookup high UID (nobody)
   result = nullptr;
-  ret =
-      LIBC_NAMESPACE::getpwuid_r(65534, &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwuid_r(65534, &pwd, buffer, sizeof(buffer), &result),
+      0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "nobody");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(65534));
@@ -65,9 +64,8 @@ TEST_F(LlvmLibcGetpwuidRTest, RootUidZero) {
   char buffer[256];
   struct passwd *result = nullptr;
 
-  int ret =
-      LIBC_NAMESPACE::getpwuid_r(0, &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwuid_r(0, &pwd, buffer, sizeof(buffer), &result), 0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "root");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(0));
@@ -82,9 +80,9 @@ TEST_F(LlvmLibcGetpwuidRTest, NotFound) {
   char buffer[256];
   struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
 
-  int ret =
-      LIBC_NAMESPACE::getpwuid_r(999, &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwuid_r(999, &pwd, buffer, sizeof(buffer), &result),
+      0);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 }
 
@@ -97,23 +95,22 @@ TEST_F(LlvmLibcGetpwuidRTest, BufferTooSmall) {
   char small_buf[8];
   struct passwd *result = reinterpret_cast<struct passwd *>(0xdeadbeef);
 
-  int ret = LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, sizeof(small_buf),
-                                       &result);
-  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, sizeof(small_buf),
+                                       &result),
+            ERANGE);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 
   // Single-byte buffer is insufficient and must return ERANGE.
   char tiny_buf[1];
   result = reinterpret_cast<struct passwd *>(0xdeadbeef);
-  ret =
-      LIBC_NAMESPACE::getpwuid_r(0, &pwd, tiny_buf, sizeof(tiny_buf), &result);
-  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwuid_r(0, &pwd, tiny_buf, sizeof(tiny_buf), &result),
+      ERANGE);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 
   // Zero-byte buffer is insufficient and must return ERANGE.
   result = reinterpret_cast<struct passwd *>(0xdeadbeef);
-  ret = LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, 0, &result);
-  ASSERT_EQ(ret, ERANGE);
+  ASSERT_EQ(LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, 0, &result), ERANGE);
   ASSERT_EQ(result, static_cast<struct passwd *>(nullptr));
 
   // Note: passing nullptr for pwd, buffer, or result is undefined behavior per
@@ -131,9 +128,8 @@ TEST_F(LlvmLibcGetpwuidRTest, BlankLines) {
   char buffer[256];
   struct passwd *result = nullptr;
 
-  int ret =
-      LIBC_NAMESPACE::getpwuid_r(1, &pwd, buffer, sizeof(buffer), &result);
-  ASSERT_EQ(ret, 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::getpwuid_r(1, &pwd, buffer, sizeof(buffer), &result), 0);
   ASSERT_EQ(result, &pwd);
   ASSERT_STREQ(pwd.pw_name, "bin");
   ASSERT_EQ(pwd.pw_uid, static_cast<uid_t>(1));
diff --git a/libc/test/src/pwd/pwd_test_utils.h b/libc/test/src/pwd/pwd_test_utils.h
index 707ea857db0e3..286dd3686da21 100644
--- a/libc/test/src/pwd/pwd_test_utils.h
+++ b/libc/test/src/pwd/pwd_test_utils.h
@@ -38,11 +38,11 @@ class ScopedPasswdFile {
       f->write(content, len);
       f->close();
     }
-    LIBC_NAMESPACE::passwd::TESTONLY_set_passwd_path(path);
+    LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(path);
   }
 
   ~ScopedPasswdFile() {
-    LIBC_NAMESPACE::passwd::TESTONLY_reset_passwd_path();
+    LIBC_NAMESPACE::pwd::TESTONLY_reset_passwd_path();
     LIBC_NAMESPACE::remove(path);
   }
 
@@ -53,7 +53,7 @@ class ScopedPasswdFile {
 class LlvmLibcPwdTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
 protected:
   void TearDown() override {
-    LIBC_NAMESPACE::passwd::TESTONLY_reset_passwd_path();
+    LIBC_NAMESPACE::pwd::TESTONLY_reset_passwd_path();
     ErrnoCheckingTest::TearDown();
   }
 };



More information about the libc-commits mailing list