344 lines
9.5 KiB
C
344 lines
9.5 KiB
C
/*
|
|
* vlibc — pwd.h/grp.h test (todo 37).
|
|
*
|
|
* Exercises the password and group databases end to end without assuming
|
|
* anything about their CONTENTS: every lookup is asserted either to
|
|
* return a sane record (string fields non-NULL, ids echo the key) or
|
|
* NULL, and every stream walk is asserted only to terminate cleanly. The
|
|
* one environment precondition is that /etc/passwd and /etc/group EXIST
|
|
* and are readable; when either is absent the whole test skips (rc 0),
|
|
* detected through a raw SYS_faccessat so no vlibc errno slot is touched
|
|
* on the skip path.
|
|
*
|
|
* Scenarios (all content-independent):
|
|
*
|
|
* 1. current-uid: getpwuid(getuid()) is NULL or a record whose pw_uid
|
|
* echoes the key and whose pw_name is set; when a record comes back,
|
|
* getpwnam(pw_name) finds it again (name round-trip).
|
|
* 2. current-gid: the same pair against the group database.
|
|
* 3. stream walk: setpwent/getpwent/endpwent; two consecutive full
|
|
* walks return the same count (the stream really rewinds), every
|
|
* record is sane, and the walk terminates.
|
|
* 4. group members: every parsed record has a non-NULL gr_mem array
|
|
* (the empty list is a single NULL entry), and member arrays
|
|
* terminate.
|
|
* 5. _r forms: getpwuid_r/getgrgid_r into a 4096-byte buffer return 0
|
|
* and either *result == NULL (key absent) or a filled struct echoing
|
|
* the key; getpwent_r/getgrent_r likewise fill one record.
|
|
* 6. NULL keys: getpwnam(NULL)/getgrnam(NULL) return NULL (defensive
|
|
* contract, no crash).
|
|
* 7. -f mode: impossible keys (uid/gid 2147483647, a name no real
|
|
* database carries) come back NULL without crashing, then the test
|
|
* leaves via a raw SYS_exit_group (house pattern: after a vlibc
|
|
* errno-capable call the host TCB may be dirty).
|
|
* 8. fgetgrent (level 2 only): parse one record from an explicitly
|
|
* fopen'd stream.
|
|
*
|
|
* Host headers are never included; <pwd.h>/<grp.h>/<unistd.h> are
|
|
* vlibc's own (-Iinclude shadows the system copies). getuid/getgid come
|
|
* from src/process/uid.c.
|
|
*/
|
|
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <unistd.h>
|
|
|
|
#include <vlibc/internal/test.h>
|
|
|
|
/* Local kernel-ABI constants for the skip probe (no public header pulled). */
|
|
#define PG_AT_FDCWD (-100)
|
|
#define PG_R_OK 4
|
|
|
|
/* Both databases readable (the only environment assumption). */
|
|
static int
|
|
pg_db_present(void)
|
|
{
|
|
return __syscall4(SYS_faccessat, PG_AT_FDCWD, (long)"/etc/passwd", PG_R_OK, 0) == 0 &&
|
|
__syscall4(SYS_faccessat, PG_AT_FDCWD, (long)"/etc/group", PG_R_OK, 0) == 0;
|
|
}
|
|
|
|
static int
|
|
test_current_uid(void)
|
|
{
|
|
uid_t uid = getuid();
|
|
struct passwd *p = getpwuid(uid);
|
|
|
|
if (p == NULL)
|
|
{
|
|
return 0; /* key absent from the database: acceptable */
|
|
}
|
|
TEST_ASSERT_TRUE(p->pw_name != NULL);
|
|
TEST_ASSERT_EQ((unsigned long)p->pw_uid, (unsigned long)uid);
|
|
if (p->pw_name != NULL)
|
|
{
|
|
struct passwd *q = getpwnam(p->pw_name);
|
|
|
|
TEST_ASSERT_TRUE(q != NULL);
|
|
if (q != NULL)
|
|
{
|
|
TEST_ASSERT_EQ((unsigned long)q->pw_uid, (unsigned long)uid);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
test_current_gid(void)
|
|
{
|
|
gid_t gid = getgid();
|
|
struct group *g = getgrgid(gid);
|
|
|
|
if (g == NULL)
|
|
{
|
|
return 0; /* key absent from the database: acceptable */
|
|
}
|
|
TEST_ASSERT_TRUE(g->gr_name != NULL);
|
|
TEST_ASSERT_EQ((unsigned long)g->gr_gid, (unsigned long)gid);
|
|
if (g->gr_name != NULL)
|
|
{
|
|
struct group *h = getgrnam(g->gr_name);
|
|
|
|
TEST_ASSERT_TRUE(h != NULL);
|
|
if (h != NULL)
|
|
{
|
|
TEST_ASSERT_EQ((unsigned long)h->gr_gid, (unsigned long)gid);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Count the records of one full pwd walk, sanity-checking each. */
|
|
static unsigned long
|
|
pg_pwd_walk_count(void)
|
|
{
|
|
struct passwd *p;
|
|
unsigned long n = 0;
|
|
|
|
setpwent();
|
|
while ((p = getpwent()) != NULL)
|
|
{
|
|
TEST_ASSERT_TRUE(p->pw_name != NULL);
|
|
if (++n > 100000UL)
|
|
{
|
|
break; /* runaway guard: the walk must terminate */
|
|
}
|
|
}
|
|
endpwent();
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
test_pwd_stream(void)
|
|
{
|
|
unsigned long first = pg_pwd_walk_count();
|
|
unsigned long second = pg_pwd_walk_count();
|
|
|
|
/* The walk terminates and setpwent really rewinds (0 == 0 for an
|
|
* empty database is still a valid equality). */
|
|
TEST_ASSERT_TRUE(first < 100000UL);
|
|
TEST_ASSERT_EQ(first, second);
|
|
|
|
/* After endpwent a fresh getpwent reopens from the start: the result
|
|
* is the first record, or NULL only when the database is empty. */
|
|
if (first != 0)
|
|
{
|
|
struct passwd *p = getpwent();
|
|
|
|
TEST_ASSERT_TRUE(p != NULL);
|
|
endpwent();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Every parsed group has a terminating gr_mem array; empty lists appear
|
|
* as a single NULL entry, so gr_mem itself is never NULL. */
|
|
static int
|
|
test_group_members(void)
|
|
{
|
|
struct group *g;
|
|
unsigned long n = 0;
|
|
|
|
setgrent();
|
|
while ((g = getgrent()) != NULL)
|
|
{
|
|
char **m;
|
|
unsigned long members = 0;
|
|
|
|
TEST_ASSERT_TRUE(g->gr_name != NULL);
|
|
TEST_ASSERT_TRUE(g->gr_mem != NULL);
|
|
for (m = g->gr_mem; m != NULL && *m != NULL; m++)
|
|
{
|
|
TEST_ASSERT_TRUE(**m != '\0');
|
|
if (++members > 100000UL)
|
|
{
|
|
break; /* runaway guard: member arrays must terminate */
|
|
}
|
|
}
|
|
TEST_ASSERT_TRUE(members < 100000UL);
|
|
if (++n > 100000UL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
endgrent();
|
|
TEST_ASSERT_TRUE(n < 100000UL);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
test_pwd_r(void)
|
|
{
|
|
struct passwd pw;
|
|
struct passwd *res = NULL;
|
|
char buf[4096];
|
|
int rc = getpwuid_r(getuid(), &pw, buf, sizeof buf, &res);
|
|
|
|
TEST_ASSERT_EQ(rc, 0);
|
|
TEST_ASSERT_TRUE(res == NULL || res == &pw);
|
|
if (res == &pw)
|
|
{
|
|
TEST_ASSERT_EQ((unsigned long)pw.pw_uid, (unsigned long)getuid());
|
|
TEST_ASSERT_TRUE(pw.pw_name != NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
test_grp_r(void)
|
|
{
|
|
struct group gr;
|
|
struct group *res = NULL;
|
|
char buf[4096];
|
|
int rc = getgrgid_r(getgid(), &gr, buf, sizeof buf, &res);
|
|
|
|
TEST_ASSERT_EQ(rc, 0);
|
|
TEST_ASSERT_TRUE(res == NULL || res == &gr);
|
|
if (res == &gr)
|
|
{
|
|
TEST_ASSERT_EQ((unsigned long)gr.gr_gid, (unsigned long)getgid());
|
|
TEST_ASSERT_TRUE(gr.gr_name != NULL);
|
|
TEST_ASSERT_TRUE(gr.gr_mem != NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
test_null_keys(void)
|
|
{
|
|
TEST_ASSERT_TRUE(getpwnam(NULL) == NULL);
|
|
TEST_ASSERT_TRUE(getgrnam(NULL) == NULL);
|
|
return 0;
|
|
}
|
|
|
|
#if VLIBC_LEVEL_GE(2)
|
|
|
|
static int
|
|
test_fgetgrent(void)
|
|
{
|
|
FILE *f = fopen("/etc/group", "r");
|
|
struct group *g;
|
|
|
|
if (f == NULL)
|
|
{
|
|
return 0; /* opened by the caller, closed by the caller */
|
|
}
|
|
g = fgetgrent(f);
|
|
if (g != NULL)
|
|
{
|
|
TEST_ASSERT_TRUE(g->gr_name != NULL);
|
|
TEST_ASSERT_TRUE(g->gr_mem != NULL);
|
|
}
|
|
(void)fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
#endif /* VLIBC_LEVEL_GE(2) */
|
|
|
|
static const struct vlibc_test tests[] = {
|
|
{"current-uid", test_current_uid},
|
|
{"current-gid", test_current_gid},
|
|
{"pwd-stream", test_pwd_stream},
|
|
{"group-members", test_group_members},
|
|
{"pwd-r", test_pwd_r},
|
|
{"grp-r", test_grp_r},
|
|
{"null-keys", test_null_keys},
|
|
#if VLIBC_LEVEL_GE(2)
|
|
{"fgetgrent", test_fgetgrent},
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* Own main (not TEST_MAIN): supports the -f failure mode. Everything else
|
|
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
|
|
*/
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
const size_t count = sizeof tests / sizeof tests[0];
|
|
size_t passed = 0;
|
|
size_t i;
|
|
|
|
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
|
{
|
|
/* Impossible keys must come back NULL without crashing. */
|
|
int before = vlibc_test_failures;
|
|
uid_t uid = (uid_t)2147483647UL;
|
|
gid_t gid = (gid_t)2147483647UL;
|
|
|
|
vlibc_test_say(1, "RUN missing-lookups: ");
|
|
TEST_ASSERT_TRUE(getpwuid(uid) == NULL);
|
|
TEST_ASSERT_TRUE(getgrgid(gid) == NULL);
|
|
TEST_ASSERT_TRUE(getpwnam("vlibc-no-such-user-xyz") == NULL);
|
|
TEST_ASSERT_TRUE(getgrnam("vlibc-no-such-group-xyz") == NULL);
|
|
if (vlibc_test_failures == before)
|
|
{
|
|
vlibc_test_say(1, "PASS\n");
|
|
vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n");
|
|
}
|
|
else
|
|
{
|
|
vlibc_test_say(1, "FAIL\n");
|
|
}
|
|
|
|
/* Raw exit: the NULL-return paths may have written the library's
|
|
* errno slot, which collides with the host TCB (house pattern). */
|
|
__syscall1(SYS_exit_group, vlibc_test_failures == before ? 0 : 1);
|
|
/* not reached */
|
|
}
|
|
|
|
if (!pg_db_present())
|
|
{
|
|
vlibc_test_say(1, "SKIP: /etc/passwd or /etc/group absent, nothing to look up\n");
|
|
__syscall1(SYS_exit_group, 0);
|
|
/* not reached */
|
|
}
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
int before = vlibc_test_failures;
|
|
|
|
vlibc_test_say(1, "RUN ");
|
|
vlibc_test_say(1, tests[i].name);
|
|
vlibc_test_say(1, ": ");
|
|
if (tests[i].run() == 0 && vlibc_test_failures == before)
|
|
{
|
|
vlibc_test_say(1, "PASS\n");
|
|
passed++;
|
|
}
|
|
else
|
|
{
|
|
vlibc_test_say(1, "FAIL\n");
|
|
}
|
|
}
|
|
|
|
vlibc_test_say(1, "SUMMARY: ");
|
|
vlibc_test_say_dec(1, (unsigned long)passed);
|
|
vlibc_test_say(1, "/");
|
|
vlibc_test_say_dec(1, (unsigned long)count);
|
|
vlibc_test_say(1, " passed, ");
|
|
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
|
|
vlibc_test_say(1, " assertion failure(s)\n");
|
|
|
|
return passed == count ? 0 : 1;
|
|
}
|