[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