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/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 36e5c7c9fb0f2..4d7bff816c57d 100644 --- a/libc/src/pwd/flat_file_db.h +++ b/libc/src/pwd/flat_file_db.h @@ -30,6 +30,8 @@ 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; }; // Forward declaration of record parser for flat database files. @@ -52,8 +54,10 @@ template class FlatFileDatabase { // fixed, bounded buffer without dynamic heap allocations or realloc. LIBC_INLINE static ErrorOr read_line(File *f, cpp::span 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 +75,8 @@ template 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 +98,7 @@ template class FlatFileDatabase { --bytes_read; buf[bytes_read] = '\0'; - return ReadLineResult{bytes_read, truncated}; + return ReadLineResult{bytes_read, truncated, eof}; } public: @@ -137,7 +143,8 @@ template 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 getnext(EntryType *entry, cpp::span buffer) { if (!entry) return Error(EINVAL); @@ -148,21 +155,27 @@ template 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 +201,16 @@ template class FlatFileDatabase { } }; +// RAII wrapper around FlatFileDatabase for stack-local database operations. +// Automatically closes the database file stream on destruction. +template +class ScopedFlatFileDatabase : public FlatFileDatabase { +public: + using FlatFileDatabase::FlatFileDatabase; + + LIBC_INLINE ~ScopedFlatFileDatabase() { this->enddb(); } +}; + } // namespace pwd } // namespace LIBC_NAMESPACE_DECL 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 new file mode 100644 index 0000000000000..6893cf769e3c1 --- /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 = pwd::find_by_name(name, pwd, cpp::span(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..1be957fa966fb --- /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 = pwd::find_by_uid(uid, pwd, cpp::span(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..cc10a21bef021 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" @@ -38,18 +39,26 @@ ErrorOr 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 +static LIBC_CONSTINIT FlatFileDatabase 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. 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 open() { return db.setdb(); } @@ -64,5 +73,25 @@ ErrorOr read_next() { return &pwd_entry; } -} // namespace passwd +ErrorOr find_by_name(cpp::string_view name, struct passwd *pwd, + cpp::span buffer, const char *path) { + ScopedFlatFileDatabase 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 find_by_uid(uid_t uid, struct passwd *pwd, cpp::span buffer, + const char *path) { + ScopedFlatFileDatabase 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 pwd } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/pwd/pwd_utils.h b/libc/src/pwd/pwd_utils.h index 4354f360e692c..249de89e258d2 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" @@ -91,13 +92,12 @@ LIBC_INLINE bool parse_line(cpp::span line, // Parses a colon-separated password database line into a struct passwd. ErrorOr 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); +// Resets the password file path back to the default. +void TESTONLY_reset_passwd_path(); + // Opens or rewinds the password file. ErrorOr open(); @@ -107,7 +107,19 @@ ErrorOr close(); // Reads the next entry from the password database. ErrorOr read_next(); -} // namespace passwd +// 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 find_by_name(cpp::string_view name, struct passwd *pwd, + cpp::span 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 find_by_uid(uid_t uid, struct passwd *pwd, cpp::span buffer, + const char *path = nullptr); + +} // 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/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 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..29313953535c6 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); @@ -143,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 @@ -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..3f9dcfa747b8b --- /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; + + 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"); + ASSERT_EQ(pwd.pw_uid, static_cast(1)); + ASSERT_EQ(pwd.pw_gid, static_cast(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 + 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(100)); + + // Lookup the last entry + result = nullptr; + 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(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(0xdeadbeef); + + ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("nonexistent", &pwd, buffer, + sizeof(buffer), &result), + 0); + ASSERT_EQ(result, static_cast(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(0xdeadbeef); + + ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf, + sizeof(small_buf), &result), + ERANGE); + ASSERT_EQ(result, static_cast(nullptr)); + + // Single-byte buffer is insufficient and must return ERANGE. + char tiny_buf[1]; + result = reinterpret_cast(0xdeadbeef); + ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("root", &pwd, tiny_buf, sizeof(tiny_buf), + &result), + ERANGE); + ASSERT_EQ(result, static_cast(nullptr)); + + // Zero-byte buffer is insufficient and must return ERANGE. + result = reinterpret_cast(0xdeadbeef); + ASSERT_EQ(LIBC_NAMESPACE::getpwnam_r("root", &pwd, small_buf, 0, &result), + ERANGE); + ASSERT_EQ(result, static_cast(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; + + 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(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..39726f97710fd --- /dev/null +++ b/libc/test/src/pwd/getpwuid_r_test.cpp @@ -0,0 +1,136 @@ +//===----------------------------------------------------------------------===// +// +// 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; + + 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(1)); + ASSERT_EQ(pwd.pw_gid, static_cast(1)); + ASSERT_STREQ(pwd.pw_dir, "/bin"); + ASSERT_STREQ(pwd.pw_shell, "/sbin/nologin"); + + // Lookup high UID (nobody) + result = nullptr; + 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(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; + + 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(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(0xdeadbeef); + + ASSERT_EQ( + LIBC_NAMESPACE::getpwuid_r(999, &pwd, buffer, sizeof(buffer), &result), + 0); + ASSERT_EQ(result, static_cast(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(0xdeadbeef); + + ASSERT_EQ(LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, sizeof(small_buf), + &result), + ERANGE); + ASSERT_EQ(result, static_cast(nullptr)); + + // Single-byte buffer is insufficient and must return ERANGE. + char tiny_buf[1]; + result = reinterpret_cast(0xdeadbeef); + ASSERT_EQ( + LIBC_NAMESPACE::getpwuid_r(0, &pwd, tiny_buf, sizeof(tiny_buf), &result), + ERANGE); + ASSERT_EQ(result, static_cast(nullptr)); + + // Zero-byte buffer is insufficient and must return ERANGE. + result = reinterpret_cast(0xdeadbeef); + ASSERT_EQ(LIBC_NAMESPACE::getpwuid_r(0, &pwd, small_buf, 0, &result), ERANGE); + ASSERT_EQ(result, static_cast(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; + + 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(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..286dd3686da21 --- /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::pwd::TESTONLY_set_passwd_path(path); + } + + ~ScopedPasswdFile() { + LIBC_NAMESPACE::pwd::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::pwd::TESTONLY_reset_passwd_path(); + ErrnoCheckingTest::TearDown(); + } +}; + +#endif // LLVM_LIBC_TEST_SRC_PWD_PWD_TEST_UTILS_H