feat(passwd): getpw*/getgr*/setpwent/endpwent family
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user