blob: ad336f50e7a7bc2e211919cfb285b17df0bbbc6b [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 getgrnam_r.
///
//===----------------------------------------------------------------------===//
#include "hdr/errno_macros.h"
#include "hdr/signal_macros.h"
#include "hdr/types/gid_t.h"
#include "hdr/types/size_t.h"
#include "hdr/types/struct_group.h"
#include "src/grp/getgrnam_r.h"
#include "src/grp/grp_utils.h"
#include "test/UnitTest/Test.h"
#include "test/src/grp/grp_test_utils.h"
TEST_F(LlvmLibcGrpTest, GetGrnamRSuccess) {
const char *content = "root:x:0:root\n"
"wheel:x:10:root,admin,user1\n";
ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_success.test"),
content);
struct group grp;
struct group *result = nullptr;
char buffer[256];
ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
&result),
0);
ASSERT_EQ(result, &grp);
EXPECT_STREQ(grp.gr_name, "wheel");
EXPECT_STREQ(grp.gr_passwd, "x");
EXPECT_EQ(grp.gr_gid, static_cast<gid_t>(10));
ASSERT_NE(grp.gr_mem, nullptr);
EXPECT_STREQ(grp.gr_mem[0], "root");
EXPECT_STREQ(grp.gr_mem[1], "admin");
EXPECT_STREQ(grp.gr_mem[2], "user1");
EXPECT_EQ(grp.gr_mem[3], nullptr);
}
TEST_F(LlvmLibcGrpTest, GetGrnamRResultLivesInCallerBuffer) {
const char *content = "wheel:x:10:root,admin\n";
ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_buf.test"),
content);
struct group grp;
struct group *result = nullptr;
char buffer[256];
ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
&result),
0);
ASSERT_NE(result, nullptr);
// POSIX requires the strings and the member array to be stored in the
// caller's buffer, not in any library-owned storage.
char *begin = buffer;
char *end = buffer + sizeof(buffer);
EXPECT_GE(grp.gr_name, begin);
EXPECT_LT(grp.gr_name, end);
EXPECT_GE(grp.gr_passwd, begin);
EXPECT_LT(grp.gr_passwd, end);
EXPECT_GE(reinterpret_cast<char *>(grp.gr_mem), begin);
EXPECT_LT(reinterpret_cast<char *>(grp.gr_mem), end);
EXPECT_GE(grp.gr_mem[0], begin);
EXPECT_LT(grp.gr_mem[0], end);
}
TEST_F(LlvmLibcGrpTest, GetGrnamRNotFound) {
const char *content = "root:x:0:root\n";
ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_absent.test"),
content);
struct group grp;
struct group *result = &grp;
char buffer[256];
ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("nosuchgroup", &grp, buffer,
sizeof(buffer), &result),
0);
// Not found is not an error: zero is returned with a null result.
ASSERT_EQ(result, nullptr);
}
TEST_F(LlvmLibcGrpTest, GetGrnamRBufferTooSmallForRecord) {
const char *content = "wheel:x:10:root,admin,user1\n";
ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_small.test"),
content);
struct group grp;
struct group *result = &grp;
char buffer[8];
ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
&result),
ERANGE);
ASSERT_EQ(result, nullptr);
}
TEST_F(LlvmLibcGrpTest, GetGrnamRBufferTooSmallForMemberArray) {
// The record fits, but the member pointer array carved out of the space
// after it does not. This must be reported as ERANGE rather than silently
// truncating the member list.
const char *content = "wheel:x:10:a,b,c,d,e,f,g,h\n";
ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_mem.test"),
content);
struct group grp;
struct group *result = &grp;
// "wheel:x:10:a,b,c,d,e,f,g,h" is 26 bytes excluding the newline. Adding 1
// for the null terminator leaves 5 bytes in a 32-byte buffer, which is too
// small for the 9 char * pointers needed by 8 members plus the terminating
// nullptr.
constexpr size_t RECORD_LEN = 26;
char buffer[RECORD_LEN + 6];
ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
&result),
ERANGE);
ASSERT_EQ(result, nullptr);
}
TEST_F(LlvmLibcGrpTest, GetGrnamRDoesNotDisturbIteration) {
const char *content = "root:x:0:root\n"
"bin:x:1:bin\n"
"wheel:x:10:admin\n";
ScopedGroupFile test_file(libc_make_test_file_path("getgrnam_r_iter.test"),
content);
const auto first = LIBC_NAMESPACE::grp::read_next();
ASSERT_TRUE(first.has_value());
ASSERT_NE(first.value(), nullptr);
EXPECT_STREQ(first.value()->gr_name, "root");
struct group grp;
struct group *result = nullptr;
char buffer[256];
ASSERT_EQ(LIBC_NAMESPACE::getgrnam_r("wheel", &grp, buffer, sizeof(buffer),
&result),
0);
ASSERT_NE(result, nullptr);
// The reentrant lookup uses its own stream, so iteration continues from
// where it left off.
const auto second = LIBC_NAMESPACE::grp::read_next();
ASSERT_TRUE(second.has_value());
ASSERT_NE(second.value(), nullptr);
EXPECT_STREQ(second.value()->gr_name, "bin");
LIBC_NAMESPACE::grp::close();
}
#if defined(LIBC_ADD_NULL_CHECKS)
TEST_F(LlvmLibcGrpTest, NullPointerCrash) {
struct group grp;
struct group *result = nullptr;
char buffer[64];
ASSERT_DEATH(
[] {
struct group local_grp;
struct group *local_result = nullptr;
char local_buf[64];
LIBC_NAMESPACE::getgrnam_r(nullptr, &local_grp, local_buf,
sizeof(local_buf), &local_result);
},
WITH_SIGNAL(-1));
ASSERT_DEATH(
[&] {
LIBC_NAMESPACE::getgrnam_r("root", nullptr, buffer, sizeof(buffer),
&result);
},
WITH_SIGNAL(-1));
ASSERT_DEATH(
[&] {
LIBC_NAMESPACE::getgrnam_r("root", &grp, buffer, sizeof(buffer),
nullptr);
},
WITH_SIGNAL(-1));
}
#endif // LIBC_ADD_NULL_CHECKS