/* * 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; // are * vlibc's own (-Iinclude shadows the system copies). getuid/getgid come * from src/process/uid.c. */ #include #include #include #include /* 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; }