[libc-commits] [libc] 606f039 - [libc] Read passwd records into a growable buffer (#224149)
via libc-commits
libc-commits at lists.llvm.org
Thu Sep 17 10:02:46 PDT 2026
Author: Jeff Bailey
Date: 2026-09-17T18:02:41+01:00
New Revision: 606f03938d7b28b2977649aa2f587647286cad75
URL: https://github.com/llvm/llvm-project/commit/606f03938d7b28b2977649aa2f587647286cad75
DIFF: https://github.com/llvm/llvm-project/commit/606f03938d7b28b2977649aa2f587647286cad75.diff
LOG: [libc] Read passwd records into a growable buffer (#224149)
Switch getpwent, getpwnam, and getpwuid to use DynamicBuffer so that
passwd records of arbitrary length are supported without fixed size
limits.
A single static DynamicBuffer and struct passwd are reused across
getpwent, getpwnam, and getpwuid per POSIX, and endpwent closes the file
stream without freeing the buffer so pointers returned prior to endpwent
remain valid.
The reentrant lookups (getpwnam_r and getpwuid_r) remain non-allocating
and return ERANGE when the caller's buffer is too small.
* Switch non-reentrant pwd lookups and iteration to DynamicBuffer
* Remove fixed 1024-byte buffer limit from pwd_utils
* Keep getpwnam_r and getpwuid_r non-allocating
* Update hermetic unit tests for long passwd records
Assisted-by: Automated tooling, human reviewed.
Added:
Modified:
libc/src/pwd/CMakeLists.txt
libc/src/pwd/pwd_utils.cpp
libc/test/src/pwd/CMakeLists.txt
libc/test/src/pwd/getpwent_test.cpp
libc/test/src/pwd/getpwnam_test.cpp
libc/test/src/pwd/getpwuid_test.cpp
Removed:
################################################################################
diff --git a/libc/src/pwd/CMakeLists.txt b/libc/src/pwd/CMakeLists.txt
index b9188bd24d8d0..9ab145a1e3592 100644
--- a/libc/src/pwd/CMakeLists.txt
+++ b/libc/src/pwd/CMakeLists.txt
@@ -128,6 +128,7 @@ add_object_library(
libc.src.__support.File.platform_file
libc.src.__support.ctype_utils
libc.src.__support.error_or
+ libc.src.__support.pwd.dynamic_buffer
libc.src.__support.pwd.field_tokenizer
libc.src.__support.pwd.flat_file_db
libc.src.__support.str_to_integer
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
index 79bdd1895b311..8d865c3a0fc45 100644
--- a/libc/src/pwd/pwd_utils.cpp
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -18,6 +18,7 @@
#include "src/__support/CPP/span.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/macros/attributes.h"
+#include "src/__support/pwd/dynamic_buffer.h"
#include "src/__support/pwd/flat_file_db.h"
#include "src/string/string_utils.h"
@@ -49,15 +50,27 @@ 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.
-static char line_buffer[1024];
-static struct passwd pwd_entry;
+//
+// A single static buffer and struct passwd are reused across getpwent,
+// getpwnam, and getpwuid per POSIX ("The return value may point to a static
+// area which is overwritten by a subsequent call to getpwent(), getpwnam(),
+// or getpwuid()"), growing only to the high-water mark of the largest record
+// seen. endpwent() closes the file stream without freeing the buffer so that
+// pointers returned prior to endpwent() remain valid until the next
+// non-reentrant call.
+static LIBC_CONSTINIT DynamicBuffer line_buffer;
+static LIBC_CONSTINIT struct passwd pwd_entry = {};
void TESTONLY_set_passwd_path(const char *path) {
+ close();
+ line_buffer.release();
passwd_file_path = path;
db.set_path(path);
}
void TESTONLY_reset_passwd_path() {
+ close();
+ line_buffer.release();
passwd_file_path = LIBC_COPT_PWD_FILE_PATH;
db.set_path(LIBC_COPT_PWD_FILE_PATH);
}
@@ -75,42 +88,57 @@ 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) {
- ScopedFlatFileDatabase<struct passwd> local_db(path ? path
- : passwd_file_path);
+namespace {
+
+// The lookups are shared between the caller-supplied fixed buffer used by the
+// reentrant entrypoints and the process-global growable buffer used by the
+// non-reentrant ones.
+template <typename BufferType>
+ErrorOr<bool> lookup_by_name(cpp::string_view name, struct passwd *pwd,
+ BufferType &buffer, const char *path) {
+ ScopedFlatFileDatabase<struct passwd> local_db(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) {
- ScopedFlatFileDatabase<struct passwd> local_db(path ? path
- : passwd_file_path);
+template <typename BufferType>
+ErrorOr<bool> lookup_by_uid(uid_t uid, struct passwd *pwd, BufferType &buffer,
+ const char *path) {
+ ScopedFlatFileDatabase<struct passwd> local_db(path);
auto matcher = [uid](const struct passwd &entry) {
return entry.pw_uid == uid;
};
return local_db.lookup(matcher, pwd, buffer);
}
+} // namespace
+
+ErrorOr<bool> find_by_name(cpp::string_view name, struct passwd *pwd,
+ cpp::span<char> buffer, const char *path) {
+ return lookup_by_name(name, pwd, buffer, path ? path : passwd_file_path);
+}
+
+ErrorOr<bool> find_by_uid(uid_t uid, struct passwd *pwd, cpp::span<char> buffer,
+ const char *path) {
+ return lookup_by_uid(uid, pwd, buffer, path ? path : passwd_file_path);
+}
+
ErrorOr<struct passwd *> find_by_name(cpp::string_view name) {
- auto res = find_by_name(name, &pwd_entry, line_buffer);
+ auto res = lookup_by_name(name, &pwd_entry, line_buffer, passwd_file_path);
if (!res.has_value())
return Error(res.error());
- bool found = res.value();
- if (!found)
+ if (!res.value())
return nullptr;
return &pwd_entry;
}
ErrorOr<struct passwd *> find_by_uid(uid_t uid) {
- auto res = find_by_uid(uid, &pwd_entry, line_buffer);
+ auto res = lookup_by_uid(uid, &pwd_entry, line_buffer, passwd_file_path);
if (!res.has_value())
return Error(res.error());
- bool found = res.value();
- if (!found)
+ if (!res.value())
return nullptr;
return &pwd_entry;
}
diff --git a/libc/test/src/pwd/CMakeLists.txt b/libc/test/src/pwd/CMakeLists.txt
index a12510e66c6d0..f60bf7016b1e5 100644
--- a/libc/test/src/pwd/CMakeLists.txt
+++ b/libc/test/src/pwd/CMakeLists.txt
@@ -26,6 +26,7 @@ add_libc_test(
getpwent_test.cpp
DEPENDS
libc.hdr.errno_macros
+ libc.hdr.types.size_t
libc.hdr.types.struct_passwd
libc.src.__support.File.file
libc.src.__support.File.platform_file
@@ -36,6 +37,7 @@ add_libc_test(
libc.src.pwd.setpwent
libc.src.stdio.remove
libc.src.string.string_utils
+ libc.test.UnitTest.ErrnoCheckingTest
)
add_libc_test(
@@ -55,10 +57,13 @@ add_libc_test(
libc.src.__support.File.file
libc.src.__support.File.platform_file
libc.src.errno.errno
+ libc.src.pwd.endpwent
+ libc.src.pwd.getpwent
libc.src.pwd.getpwnam
libc.src.pwd.pwd_utils
libc.src.stdio.remove
libc.src.string.string_utils
+ libc.test.UnitTest.ErrnoCheckingTest
)
add_libc_test(
@@ -81,6 +86,7 @@ add_libc_test(
libc.src.pwd.pwd_utils
libc.src.stdio.remove
libc.src.string.string_utils
+ libc.test.UnitTest.ErrnoCheckingTest
)
add_libc_test(
@@ -104,6 +110,7 @@ add_libc_test(
libc.src.pwd.pwd_utils
libc.src.stdio.remove
libc.src.string.string_utils
+ libc.test.UnitTest.ErrnoCheckingTest
)
add_libc_test(
@@ -126,4 +133,5 @@ add_libc_test(
libc.src.pwd.pwd_utils
libc.src.stdio.remove
libc.src.string.string_utils
+ libc.test.UnitTest.ErrnoCheckingTest
)
diff --git a/libc/test/src/pwd/getpwent_test.cpp b/libc/test/src/pwd/getpwent_test.cpp
index 29313953535c6..356307ac230ce 100644
--- a/libc/test/src/pwd/getpwent_test.cpp
+++ b/libc/test/src/pwd/getpwent_test.cpp
@@ -13,16 +13,13 @@
#include "hdr/errno_macros.h"
#include "hdr/types/struct_passwd.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 "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/Test.h"
-
-using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
+#include "test/src/pwd/pwd_test_utils.h"
TEST_F(LlvmLibcPwdTest, GetPwentTestSuccess) {
const char *content = "root:x:0:0:root:/root:/bin/bash\n"
@@ -33,17 +30,17 @@ TEST_F(LlvmLibcPwdTest, GetPwentTestSuccess) {
LIBC_NAMESPACE::setpwent();
struct passwd *pwd1 = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd1 != nullptr);
+ ASSERT_NE(pwd1, nullptr);
ASSERT_STREQ(pwd1->pw_name, "root");
ASSERT_EQ(pwd1->pw_uid, 0u);
struct passwd *pwd2 = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd2 != nullptr);
+ ASSERT_NE(pwd2, nullptr);
ASSERT_STREQ(pwd2->pw_name, "bin");
ASSERT_EQ(pwd2->pw_uid, 1u);
struct passwd *pwd3 = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd3 == nullptr);
+ ASSERT_EQ(pwd3, nullptr);
LIBC_NAMESPACE::endpwent();
}
@@ -56,7 +53,7 @@ TEST_F(LlvmLibcPwdTest, GetPwentTestFailure) {
LIBC_NAMESPACE::setpwent();
struct passwd *pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd == nullptr);
+ ASSERT_EQ(pwd, nullptr);
ASSERT_ERRNO_EQ(EINVAL);
LIBC_NAMESPACE::endpwent();
@@ -69,18 +66,18 @@ TEST_F(LlvmLibcPwdTest, SetPwentTestHermetic) {
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd != nullptr);
+ ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "user1");
pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd != nullptr);
+ ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "user2");
// Reset iteration
LIBC_NAMESPACE::setpwent();
pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd != nullptr);
+ ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "user1");
LIBC_NAMESPACE::endpwent();
@@ -92,25 +89,26 @@ TEST_F(LlvmLibcPwdTest, ReopenAfterEndpwent) {
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd != nullptr);
+ ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "root");
LIBC_NAMESPACE::endpwent();
pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd != nullptr);
+ ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "root");
LIBC_NAMESPACE::endpwent();
}
TEST_F(LlvmLibcPwdTest, FileOpenFailure) {
- LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(
- "/nonexistent_directory/nonexistent_file");
+ auto missing_path =
+ libc_make_test_file_path("nonexistent_dir/getpwent_missing.test");
+ LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(missing_path);
LIBC_NAMESPACE::endpwent(); // Force close any existing file
struct passwd *pwd = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd == nullptr);
+ ASSERT_EQ(pwd, nullptr);
ASSERT_ERRNO_EQ(ENOENT);
}
@@ -122,15 +120,86 @@ TEST_F(LlvmLibcPwdTest, BlankLines) {
LIBC_NAMESPACE::setpwent();
struct passwd *pwd1 = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd1 != nullptr);
+ ASSERT_NE(pwd1, nullptr);
ASSERT_STREQ(pwd1->pw_name, "root");
struct passwd *pwd2 = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd2 != nullptr);
+ ASSERT_NE(pwd2, nullptr);
ASSERT_STREQ(pwd2->pw_name, "bin");
struct passwd *pwd3 = LIBC_NAMESPACE::getpwent();
- ASSERT_TRUE(pwd3 == nullptr);
+ ASSERT_EQ(pwd3, nullptr);
+
+ LIBC_NAMESPACE::endpwent();
+}
+
+TEST_F(LlvmLibcPwdTest, LongLineGrowsBuffer) {
+ // Record 0 is medium-sized (600 bytes) and record 1 is large (3,000 bytes)
+ // so that the second getpwent call must grow the iteration buffer beyond
+ // the capacity allocated for the first call.
+ constexpr size_t GECOS_LENGTH_0 = 600;
+ constexpr size_t GECOS_LENGTH_1 = 3000;
+ constexpr size_t RECORD_OVERHEAD = 64;
+ constexpr size_t CONTENT_BUFFER_SIZE =
+ GECOS_LENGTH_0 + GECOS_LENGTH_1 + 2 * RECORD_OVERHEAD;
+ char content[CONTENT_BUFFER_SIZE];
+
+ size_t pos = 0;
+ for (size_t record = 0; record < 2; ++record) {
+ const char *prefix = record == 0 ? "user0:x:100:100:" : "user1:x:101:101:";
+ size_t gecos_len = record == 0 ? GECOS_LENGTH_0 : GECOS_LENGTH_1;
+ for (const char *p = prefix; *p != '\0'; ++p)
+ content[pos++] = *p;
+ for (size_t i = 0; i < gecos_len; ++i)
+ content[pos++] = 'g';
+ for (const char *p = ":/home/user:/bin/sh\n"; *p != '\0'; ++p)
+ content[pos++] = *p;
+ }
+ content[pos] = '\0';
+
+ ScopedPasswdFile test_file(libc_make_test_file_path("getpwent_longline.test"),
+ content);
+
+ LIBC_NAMESPACE::setpwent();
+
+ struct passwd *pwd1 = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd1, nullptr);
+ ASSERT_STREQ(pwd1->pw_name, "user0");
+ ASSERT_EQ(LIBC_NAMESPACE::internal::string_length(pwd1->pw_gecos),
+ GECOS_LENGTH_0);
+
+ struct passwd *pwd2 = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd2, nullptr);
+ ASSERT_STREQ(pwd2->pw_name, "user1");
+ ASSERT_EQ(LIBC_NAMESPACE::internal::string_length(pwd2->pw_gecos),
+ GECOS_LENGTH_1);
+ ASSERT_STREQ(pwd2->pw_shell, "/bin/sh");
+
+ ASSERT_EQ(LIBC_NAMESPACE::getpwent(), nullptr);
+
+ LIBC_NAMESPACE::endpwent();
+}
+
+TEST_F(LlvmLibcPwdTest, EndPwentClosesStreamAndIterationRestarts) {
+ 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("getpwent_reopen.test"),
+ content);
+
+ LIBC_NAMESPACE::setpwent();
+ struct passwd *pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "root");
+
+ // endpwent closes the file stream without freeing the static buffer, so the
+ // last returned pointer remains valid and the next iteration reopens from
+ // the top.
+ LIBC_NAMESPACE::endpwent();
+ ASSERT_STREQ(pwd->pw_name, "root");
+
+ pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "root");
LIBC_NAMESPACE::endpwent();
}
diff --git a/libc/test/src/pwd/getpwnam_test.cpp b/libc/test/src/pwd/getpwnam_test.cpp
index 17b70b9215ebd..752d5867facbd 100644
--- a/libc/test/src/pwd/getpwnam_test.cpp
+++ b/libc/test/src/pwd/getpwnam_test.cpp
@@ -15,11 +15,13 @@
#include "hdr/types/gid_t.h"
#include "hdr/types/struct_passwd.h"
#include "hdr/types/uid_t.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/getpwnam.h"
#include "src/pwd/pwd_utils.h"
#include "test/UnitTest/Test.h"
+#include "test/src/pwd/pwd_test_utils.h"
using LlvmLibcGetpwnamTest = LlvmLibcPwdTest;
@@ -90,29 +92,77 @@ TEST_F(LlvmLibcGetpwnamTest, BlankLines) {
}
TEST_F(LlvmLibcGetpwnamTest, FileOpenFailure) {
- LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(
- "/nonexistent_directory/nonexistent_file");
+ auto missing_path =
+ libc_make_test_file_path("nonexistent_dir/getpwnam_missing.test");
+ LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(missing_path);
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("root");
ASSERT_EQ(pwd, nullptr);
ASSERT_ERRNO_EQ(ENOENT);
}
-TEST_F(LlvmLibcGetpwnamTest, BufferTooSmall) {
- // A line exceeding line_buffer (1024 bytes) triggers ERANGE.
- char content[1100];
+TEST_F(LlvmLibcGetpwnamTest, LongLineGrowsBuffer) {
+ // A multi-kilobyte record requires dynamic growth of the lookup buffer to
+ // return the entry in full.
+ constexpr size_t GECOS_LENGTH = 3000;
+ constexpr size_t CONTENT_BUFFER_SIZE = GECOS_LENGTH + 128;
+ char content[CONTENT_BUFFER_SIZE];
LIBC_NAMESPACE::internal::strlcpy(content,
"longuser:x:1000:1000:", sizeof(content));
- size_t cur = LIBC_NAMESPACE::internal::string_length(content);
- for (; cur < 1050; ++cur)
- content[cur] = 'a';
+ size_t prefix_len = LIBC_NAMESPACE::internal::string_length(content);
+ size_t cur = prefix_len;
+ for (size_t i = 0; i < GECOS_LENGTH; ++i)
+ content[cur++] = 'a';
LIBC_NAMESPACE::internal::strlcpy(content + cur, ":/home/longuser:/bin/sh\n",
sizeof(content) - cur);
- ScopedPasswdFile test_file(libc_make_test_file_path("getpwnam_toosmall.test"),
+ ScopedPasswdFile test_file(libc_make_test_file_path("getpwnam_longline.test"),
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("longuser");
- ASSERT_EQ(pwd, nullptr);
- ASSERT_ERRNO_EQ(ERANGE);
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "longuser");
+ ASSERT_EQ(pwd->pw_uid, static_cast<uid_t>(1000));
+ ASSERT_STREQ(pwd->pw_dir, "/home/longuser");
+ ASSERT_STREQ(pwd->pw_shell, "/bin/sh");
+ ASSERT_EQ(LIBC_NAMESPACE::internal::string_length(pwd->pw_gecos),
+ GECOS_LENGTH);
+}
+
+TEST_F(LlvmLibcGetpwnamTest, DoesNotDisturbIteration) {
+ 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_iter.test"),
+ content);
+
+ struct passwd *pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "root");
+
+ struct passwd *found = LIBC_NAMESPACE::getpwnam("daemon");
+ ASSERT_NE(found, nullptr);
+ ASSERT_STREQ(found->pw_name, "daemon");
+
+ // Closing the iteration stream via endpwent does not invalidate the pointer
+ // returned by getpwnam.
+ LIBC_NAMESPACE::endpwent();
+ ASSERT_STREQ(found->pw_name, "daemon");
+
+ // Reopening iteration starts from the beginning.
+ pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "root");
+
+ // A mid-iteration getpwnam lookup opens its own scoped stream so the
+ // getpwent stream position is undisturbed.
+ found = LIBC_NAMESPACE::getpwnam("daemon");
+ ASSERT_NE(found, nullptr);
+ ASSERT_STREQ(found->pw_name, "daemon");
+
+ pwd = LIBC_NAMESPACE::getpwent();
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "bin");
+
+ LIBC_NAMESPACE::endpwent();
}
diff --git a/libc/test/src/pwd/getpwuid_test.cpp b/libc/test/src/pwd/getpwuid_test.cpp
index ac12e78a762fc..c6ad80d00ab6f 100644
--- a/libc/test/src/pwd/getpwuid_test.cpp
+++ b/libc/test/src/pwd/getpwuid_test.cpp
@@ -15,11 +15,11 @@
#include "hdr/types/gid_t.h"
#include "hdr/types/struct_passwd.h"
#include "hdr/types/uid_t.h"
-#include "pwd_test_utils.h"
#include "src/__support/libc_errno.h"
#include "src/pwd/getpwuid.h"
#include "src/pwd/pwd_utils.h"
#include "test/UnitTest/Test.h"
+#include "test/src/pwd/pwd_test_utils.h"
using LlvmLibcGetpwuidTest = LlvmLibcPwdTest;
@@ -89,29 +89,38 @@ TEST_F(LlvmLibcGetpwuidTest, BlankLines) {
}
TEST_F(LlvmLibcGetpwuidTest, FileOpenFailure) {
- LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(
- "/nonexistent_directory/nonexistent_file");
+ auto missing_path =
+ libc_make_test_file_path("nonexistent_dir/getpwuid_missing.test");
+ LIBC_NAMESPACE::pwd::TESTONLY_set_passwd_path(missing_path);
struct passwd *pwd = LIBC_NAMESPACE::getpwuid(0);
ASSERT_EQ(pwd, nullptr);
ASSERT_ERRNO_EQ(ENOENT);
}
-TEST_F(LlvmLibcGetpwuidTest, BufferTooSmall) {
- // A line exceeding line_buffer (1024 bytes) triggers ERANGE.
- char content[1100];
+TEST_F(LlvmLibcGetpwuidTest, LongLineGrowsBuffer) {
+ // A multi-kilobyte record requires dynamic growth of the lookup buffer to
+ // return the entry in full.
+ constexpr size_t GECOS_LENGTH = 3000;
+ constexpr size_t CONTENT_BUFFER_SIZE = GECOS_LENGTH + 128;
+ char content[CONTENT_BUFFER_SIZE];
LIBC_NAMESPACE::internal::strlcpy(content,
"longuser:x:1000:1000:", sizeof(content));
- size_t cur = LIBC_NAMESPACE::internal::string_length(content);
- for (; cur < 1050; ++cur)
- content[cur] = 'a';
+ size_t prefix_len = LIBC_NAMESPACE::internal::string_length(content);
+ size_t cur = prefix_len;
+ for (size_t i = 0; i < GECOS_LENGTH; ++i)
+ content[cur++] = 'a';
LIBC_NAMESPACE::internal::strlcpy(content + cur, ":/home/longuser:/bin/sh\n",
sizeof(content) - cur);
- ScopedPasswdFile test_file(libc_make_test_file_path("getpwuid_toosmall.test"),
+ ScopedPasswdFile test_file(libc_make_test_file_path("getpwuid_longline.test"),
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwuid(1000);
- ASSERT_EQ(pwd, nullptr);
- ASSERT_ERRNO_EQ(ERANGE);
+ ASSERT_NE(pwd, nullptr);
+ ASSERT_STREQ(pwd->pw_name, "longuser");
+ ASSERT_STREQ(pwd->pw_dir, "/home/longuser");
+ ASSERT_STREQ(pwd->pw_shell, "/bin/sh");
+ ASSERT_EQ(LIBC_NAMESPACE::internal::string_length(pwd->pw_gecos),
+ GECOS_LENGTH);
}
More information about the libc-commits
mailing list