blob: 752d5867facbd76efeb9d809686a9549f167ad88 [file] [edit]
//===----------------------------------------------------------------------===//
//
// 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.
///
//===----------------------------------------------------------------------===//
#include "hdr/errno_macros.h"
#include "hdr/types/gid_t.h"
#include "hdr/types/struct_passwd.h"
#include "hdr/types/uid_t.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;
TEST_F(LlvmLibcGetpwnamTest, 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_success.test"),
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("bin");
ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "bin");
ASSERT_STREQ(pwd->pw_passwd, "x");
ASSERT_EQ(pwd->pw_uid, static_cast<uid_t>(1));
ASSERT_EQ(pwd->pw_gid, static_cast<gid_t>(1));
ASSERT_STREQ(pwd->pw_gecos, "bin");
ASSERT_STREQ(pwd->pw_dir, "/bin");
ASSERT_STREQ(pwd->pw_shell, "/sbin/nologin");
}
TEST_F(LlvmLibcGetpwnamTest, 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_boundary.test"),
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("first");
ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "first");
ASSERT_EQ(pwd->pw_uid, static_cast<uid_t>(100));
pwd = LIBC_NAMESPACE::getpwnam("last");
ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "last");
ASSERT_EQ(pwd->pw_uid, static_cast<uid_t>(102));
}
TEST_F(LlvmLibcGetpwnamTest, NotFound) {
const char *content = "root:x:0:0:root:/root:/bin/bash\n";
ScopedPasswdFile test_file(libc_make_test_file_path("getpwnam_notfound.test"),
content);
// POSIX specifies that errno must not be changed when an entry is not found.
// Pre-set errno to confirm it remains untouched across the call.
LIBC_NAMESPACE::libc_errno = ENOENT;
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("nonexistent");
ASSERT_EQ(pwd, nullptr);
ASSERT_ERRNO_EQ(ENOENT);
// When errno is initially zero, it remains zero.
pwd = LIBC_NAMESPACE::getpwnam("nonexistent");
ASSERT_EQ(pwd, nullptr);
ASSERT_ERRNO_SUCCESS();
}
TEST_F(LlvmLibcGetpwnamTest, 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_blank.test"),
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("bin");
ASSERT_NE(pwd, nullptr);
ASSERT_STREQ(pwd->pw_name, "bin");
ASSERT_EQ(pwd->pw_uid, static_cast<uid_t>(1));
}
TEST_F(LlvmLibcGetpwnamTest, FileOpenFailure) {
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, 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 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_longline.test"),
content);
struct passwd *pwd = LIBC_NAMESPACE::getpwnam("longuser");
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();
}