[libc-commits] [libc] [libc] Add struct passwd type, pwd.yaml, and pwd_utils parser (PR #212421)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Tue Jul 28 01:06:40 PDT 2026


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

>From 81a80cd48200395b7a3941ddb4d11165d9207c82 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Tue, 28 Jul 2026 09:06:26 +0100
Subject: [PATCH] [libc] Add struct passwd type, pwd.yaml, and pwd_utils parser

Added struct passwd type definition, pwd.yaml header specification, and
parse_passwd_line utility returning ErrorOr<struct passwd> for colon-separated
password file parsing with hermetic unit tests.

* Added libc/include/llvm-libc-types/struct_passwd.h and proxy header
* Added libc/include/pwd.yaml header specification
* Added pwd_utils.h and pwd_utils.cpp implementing ErrorOr<struct passwd> parse_passwd_line
* Added pwd.h to target public headers for linux architectures
* Added unit tests in libc/test/src/pwd/pwd_utils_test.cpp

Assisted-by: Automated tooling, human reviewed.
---
 libc/config/linux/aarch64/headers.txt        |   1 +
 libc/config/linux/arm/headers.txt            |   1 +
 libc/config/linux/i386/headers.txt           |   1 +
 libc/config/linux/riscv/headers.txt          |   1 +
 libc/config/linux/x86_64/headers.txt         |   1 +
 libc/hdr/types/CMakeLists.txt                |   8 ++
 libc/hdr/types/struct_passwd.h               |  26 +++++
 libc/include/CMakeLists.txt                  |  11 ++
 libc/include/llvm-libc-types/CMakeLists.txt  |   1 +
 libc/include/llvm-libc-types/struct_passwd.h |  31 ++++++
 libc/include/pwd.yaml                        |  26 +++++
 libc/src/CMakeLists.txt                      |   1 +
 libc/src/pwd/CMakeLists.txt                  |  14 +++
 libc/src/pwd/pwd_utils.cpp                   |  72 +++++++++++++
 libc/src/pwd/pwd_utils.h                     |  35 +++++++
 libc/test/src/CMakeLists.txt                 |   1 +
 libc/test/src/pwd/CMakeLists.txt             |  13 +++
 libc/test/src/pwd/pwd_utils_test.cpp         | 104 +++++++++++++++++++
 18 files changed, 348 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/pwd_utils.cpp
 create mode 100644 libc/src/pwd/pwd_utils.h
 create mode 100644 libc/test/src/pwd/CMakeLists.txt
 create mode 100644 libc/test/src/pwd/pwd_utils_test.cpp

diff --git a/libc/config/linux/aarch64/headers.txt b/libc/config/linux/aarch64/headers.txt
index 7d3e65c2434c7..b413e43f21446 100644
--- a/libc/config/linux/aarch64/headers.txt
+++ b/libc/config/linux/aarch64/headers.txt
@@ -30,6 +30,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.netinet_udp
     libc.include.poll
     libc.include.pthread
+    libc.include.pwd
     libc.include.sched
     libc.include.search
     libc.include.setjmp
diff --git a/libc/config/linux/arm/headers.txt b/libc/config/linux/arm/headers.txt
index eff8ea665d0c8..42580bfb10b2a 100644
--- a/libc/config/linux/arm/headers.txt
+++ b/libc/config/linux/arm/headers.txt
@@ -20,6 +20,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.stdlib
     libc.include.string
     libc.include.strings
+    libc.include.pwd
     libc.include.uchar
     libc.include.wchar
     libc.include.wctype
diff --git a/libc/config/linux/i386/headers.txt b/libc/config/linux/i386/headers.txt
index def05189f136d..1235db6e0b119 100644
--- a/libc/config/linux/i386/headers.txt
+++ b/libc/config/linux/i386/headers.txt
@@ -1,6 +1,7 @@
 set(TARGET_PUBLIC_HEADERS
   libc.include.alloca
   libc.include.assert
+  libc.include.pwd
   libc.include.cpio
   libc.include.langinfo
 )
diff --git a/libc/config/linux/riscv/headers.txt b/libc/config/linux/riscv/headers.txt
index 199b0fa1e47c2..8b9b7c101a63f 100644
--- a/libc/config/linux/riscv/headers.txt
+++ b/libc/config/linux/riscv/headers.txt
@@ -31,6 +31,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.nl_types
     libc.include.poll
     libc.include.pthread
+    libc.include.pwd
     libc.include.sched
     libc.include.search
     libc.include.setjmp
diff --git a/libc/config/linux/x86_64/headers.txt b/libc/config/linux/x86_64/headers.txt
index 89090501c3a04..d36089c3da8ff 100644
--- a/libc/config/linux/x86_64/headers.txt
+++ b/libc/config/linux/x86_64/headers.txt
@@ -31,6 +31,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.nl_types
     libc.include.poll
     libc.include.pthread
+    libc.include.pwd
     libc.include.sched
     libc.include.search
     libc.include.setjmp
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 0c926050221f4..40833e0857478 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -153,6 +153,14 @@ add_proxy_header_library(
     libc.include.llvm-libc-types.struct_tm
 )
 
+add_proxy_header_library(
+  struct_passwd
+  HDRS
+    struct_passwd.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.struct_passwd
+)
+
 add_proxy_header_library(
   rsize_t
   HDRS
diff --git a/libc/hdr/types/struct_passwd.h b/libc/hdr/types/struct_passwd.h
new file mode 100644
index 0000000000000..cf82d6bca9b90
--- /dev/null
+++ b/libc/hdr/types/struct_passwd.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
+/// 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 0f161f46af471..d51668bbdc1e5 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -490,6 +490,17 @@ add_header_macro(
     .llvm_libc_common_h
 )
 
+add_header_macro(
+  pwd
+  ../libc/include/pwd.yaml
+  pwd.h
+  DEPENDS
+    .llvm-libc-types.struct_passwd
+    .llvm-libc-types.uid_t
+    .llvm-libc-types.gid_t
+    .llvm_libc_common_h
+)
+
 if(LIBC_TARGET_ARCHITECTURE STREQUAL "x86_64")
   add_header_macro(
     ucontext
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 9dd7548aa5126..b6d7d14e81723 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -196,6 +196,7 @@ add_header(
     .blksize_t .blkcnt_t
 )
 add_header(struct_tm HDR struct_tm.h)
+add_header(struct_passwd HDR struct_passwd.h DEPENDS .uid_t .gid_t)
 add_header(struct_utsname HDR struct_utsname.h)
 add_header(thrd_start_t HDR thrd_start_t.h)
 add_header(thrd_t HDR thrd_t.h DEPENDS .__thread_type)
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..c51d3349f7c87
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct_passwd.h
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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"
+
+/// Structure representing user account information from the password database.
+struct passwd {
+  char *pw_name;   // User's login name.
+  char *pw_passwd; // Encrypted password.
+  uid_t pw_uid;    // Numerical user ID.
+  gid_t pw_gid;    // Numerical group ID.
+  char *pw_gecos;  // User info / real name.
+  char *pw_dir;    // Initial working directory.
+  char *pw_shell;  // Program to use as 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..40db5690e98b9
--- /dev/null
+++ b/libc/include/pwd.yaml
@@ -0,0 +1,26 @@
+header: pwd.h
+standards:
+  - posix
+types:
+  - type_name: struct_passwd
+  - type_name: gid_t
+  - type_name: uid_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 2c7c4d00f150a..05944b582b5f8 100644
--- a/libc/src/CMakeLists.txt
+++ b/libc/src/CMakeLists.txt
@@ -56,3 +56,4 @@ add_subdirectory(signal)
 add_subdirectory(spawn)
 add_subdirectory(threads)
 add_subdirectory(ucontext)
+add_subdirectory(pwd)
diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
new file mode 100644
index 0000000000000..9670bbd60f607
--- /dev/null
+++ b/libc/src/pwd/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_object_library(
+  pwd_utils
+  HDRS
+    pwd_utils.h
+  SRCS
+    pwd_utils.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.types.struct_passwd
+    libc.src.string.string_utils
+    libc.src.__support.ctype_utils
+    libc.src.__support.str_to_integer
+    libc.src.__support.macros.config
+)
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
new file mode 100644
index 0000000000000..a0a103a81a46b
--- /dev/null
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "hdr/errno_macros.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/str_to_integer.h"
+#include "src/string/string_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+ErrorOr<struct passwd> parse_passwd_line(char *line) {
+  if (!line)
+    return Error(EINVAL);
+
+  struct passwd pwd;
+  char *context = line;
+
+  pwd.pw_name = string_token<false>(nullptr, ":", &context);
+  if (!pwd.pw_name)
+    return Error(EINVAL);
+
+  pwd.pw_passwd = string_token<false>(nullptr, ":", &context);
+  if (!pwd.pw_passwd)
+    return Error(EINVAL);
+
+  char *uid_str = string_token<false>(nullptr, ":", &context);
+  if (!uid_str || !isdigit(uid_str[0]))
+    return Error(EINVAL);
+  auto uid_res = strtointeger<uid_t>(uid_str, 10);
+  if (uid_res.has_error() || uid_res.parsed_len == 0 ||
+      uid_str[uid_res.parsed_len] != '\0')
+    return Error(EINVAL);
+  pwd.pw_uid = uid_res.value;
+
+  char *gid_str = string_token<false>(nullptr, ":", &context);
+  if (!gid_str || !isdigit(gid_str[0]))
+    return Error(EINVAL);
+  auto gid_res = strtointeger<gid_t>(gid_str, 10);
+  if (gid_res.has_error() || gid_res.parsed_len == 0 ||
+      gid_str[gid_res.parsed_len] != '\0')
+    return Error(EINVAL);
+  pwd.pw_gid = gid_res.value;
+
+  pwd.pw_gecos = string_token<false>(nullptr, ":", &context);
+  if (!pwd.pw_gecos)
+    return Error(EINVAL);
+
+  pwd.pw_dir = string_token<false>(nullptr, ":", &context);
+  if (!pwd.pw_dir)
+    return Error(EINVAL);
+
+  pwd.pw_shell = string_token<false>(nullptr, ":", &context);
+  if (!pwd.pw_shell)
+    return Error(EINVAL);
+
+  return pwd;
+}
+
+} // 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..9509fea245529
--- /dev/null
+++ b/libc/src/pwd/pwd_utils.h
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/error_or.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+/// Parses a colon-separated password database line into a struct passwd.
+///
+/// \param[in,out] line Pointer to the null-terminated string to parse.
+///                     Modified in-place during tokenization.
+/// \return ErrorOr<struct passwd> containing the parsed entry on success, or
+///         an Error (e.g. EINVAL) on failure.
+ErrorOr<struct passwd> parse_passwd_line(char *line);
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PWD_PWD_UTILS_H
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index 25a5c920036c3..01d6ee738135c 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -110,6 +110,7 @@ add_subdirectory(nl_types)
 add_subdirectory(signal)
 add_subdirectory(regex)
 add_subdirectory(spawn)
+add_subdirectory(pwd)
 
 if(${LIBC_TARGET_OS} STREQUAL "linux")
   add_subdirectory(pthread)
diff --git a/libc/test/src/pwd/CMakeLists.txt b/libc/test/src/pwd/CMakeLists.txt
new file mode 100644
index 0000000000000..eb6c96f2f296f
--- /dev/null
+++ b/libc/test/src/pwd/CMakeLists.txt
@@ -0,0 +1,13 @@
+add_custom_target(libc_pwd_unittests)
+
+add_libc_unittest(
+  pwd_utils_test
+  SUITE
+    libc_pwd_unittests
+  SRCS
+    pwd_utils_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.types.struct_passwd
+    libc.src.pwd.pwd_utils
+)
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..528577f830e87
--- /dev/null
+++ b/libc/test/src/pwd/pwd_utils_test.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/errno_macros.h"
+#include "hdr/types/struct_passwd.h"
+#include "src/pwd/pwd_utils.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_Success) {
+  char line[] = "root:x:0:0:root:/root:/bin/bash";
+  auto res = LIBC_NAMESPACE::internal::parse_passwd_line(line);
+  ASSERT_TRUE(res.has_value());
+  struct passwd pwd = res.value();
+  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:";
+  auto res = LIBC_NAMESPACE::internal::parse_passwd_line(line);
+  ASSERT_TRUE(res.has_value());
+  struct passwd pwd = res.value();
+  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";
+  auto res1 = LIBC_NAMESPACE::internal::parse_passwd_line(line1);
+  ASSERT_FALSE(res1.has_value());
+  ASSERT_EQ(res1.error(), EINVAL);
+
+  char line2[] = "root:x:0:def:root:/root:/bin/bash";
+  auto res2 = LIBC_NAMESPACE::internal::parse_passwd_line(line2);
+  ASSERT_FALSE(res2.has_value());
+  ASSERT_EQ(res2.error(), EINVAL);
+
+  char line3[] = "root:x:-1:0:root:/root:/bin/bash";
+  auto res3 = LIBC_NAMESPACE::internal::parse_passwd_line(line3);
+  ASSERT_FALSE(res3.has_value());
+  ASSERT_EQ(res3.error(), EINVAL);
+
+  char line4[] = "root:x:0:-1:root:/root:/bin/bash";
+  auto res4 = LIBC_NAMESPACE::internal::parse_passwd_line(line4);
+  ASSERT_FALSE(res4.has_value());
+  ASSERT_EQ(res4.error(), EINVAL);
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_MissingFields) {
+  char line[] = "root:x:0:0:root:/root";
+  auto res = LIBC_NAMESPACE::internal::parse_passwd_line(line);
+  ASSERT_FALSE(res.has_value());
+  ASSERT_EQ(res.error(), EINVAL);
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_NullInput) {
+  auto res = LIBC_NAMESPACE::internal::parse_passwd_line(nullptr);
+  ASSERT_FALSE(res.has_value());
+  ASSERT_EQ(res.error(), EINVAL);
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_TrailingGarbage) {
+  char line1[] = "root:x:0a:0:root:/root:/bin/bash";
+  auto res1 = LIBC_NAMESPACE::internal::parse_passwd_line(line1);
+  ASSERT_FALSE(res1.has_value());
+  ASSERT_EQ(res1.error(), EINVAL);
+
+  char line2[] = "root:x:0:0b:root:/root:/bin/bash";
+  auto res2 = LIBC_NAMESPACE::internal::parse_passwd_line(line2);
+  ASSERT_FALSE(res2.has_value());
+  ASSERT_EQ(res2.error(), EINVAL);
+}
+
+TEST(LlvmLibcPwdTest, ParsePasswdLine_Overflow) {
+  char line1[] = "root:x:4294967296:0:root:/root:/bin/bash";
+  auto res1 = LIBC_NAMESPACE::internal::parse_passwd_line(line1);
+  ASSERT_FALSE(res1.has_value());
+  ASSERT_EQ(res1.error(), EINVAL);
+
+  char line2[] = "root:x:0:4294967296:root:/root:/bin/bash";
+  auto res2 = LIBC_NAMESPACE::internal::parse_passwd_line(line2);
+  ASSERT_FALSE(res2.has_value());
+  ASSERT_EQ(res2.error(), EINVAL);
+}



More information about the libc-commits mailing list