/* * vlibc — unistd misc / libgen / realpath / confstr test (todo 38). * * Coverage: * * 1. getcwd correctness: the result matches the raw SYS_getcwd answer, * and the getcwd(NULL, 0) malloc form returns the same string; * 2. terminal/identity probes: isatty on invalid descriptors is 0, * ttyname(1) is NULL or a "/..." device path, ttyname_r returns 0 * or ENOTTY; * 3. getopt round-trips: bundled options, separate and attached * arguments, "--" termination, silent ':' and '?' error modes, and * optind landing on the first non-option; * 4. basename/dirname golden cases including the trailing-slash and * degenerate "/" / "" names; * 5. confstr: _CS_PATH queries return a length, copy the value into a * big buffer and report the same length when the buffer is small; * _CS_GNU_LIBC_VERSION is the empty string; * 6. realpath: "." and "/" resolve correctly (also in the malloc'd * form); getwd matches getcwd; swab swaps adjacent bytes. * * -f mode: the failure shapes — realpath(NULL) and of a nonexistent * name, and getcwd(buf, 0) — return NULL without crashing. * * The whole body is level 2 (libgen/realpath/confstr are XSI; only the * L1 functions appear inside it); at level 1 this TU compiles to a SKIP * runner. The main always leaves through a raw SYS_exit_group: several * probes (ttyname, realpath, getcwd failures) write the library's errno * slot, which collides with the host TCB, so host cleanup must never * run after them (house pattern). * * No host headers are included (-Iinclude shadows them); argv arrays are * plain writable char*[] so getopt can scan them. */ #include #include #include #include #include #include #if VLIBC_LEVEL_GE(2) /* Reset the getopt globals between scenarios. */ static void uopt_reset(void) { optarg = NULL; optind = 1; opterr = 0; optopt = 0; } static int test_getcwd(void) { char kern[4096]; char mine[4096]; char *dyn; long n; n = __syscall2(SYS_getcwd, (long)kern, (long)sizeof(kern)); TEST_ASSERT_TRUE(n > 0); TEST_ASSERT_TRUE(getcwd(mine, sizeof(mine)) == mine); TEST_ASSERT_STREQ(mine, kern); dyn = getcwd(NULL, 0); TEST_ASSERT_TRUE(dyn != NULL); if (dyn != NULL) { TEST_ASSERT_STREQ(dyn, kern); free(dyn); } return 0; } static int test_isatty_ttyname(void) { char name[256]; char *t; int rc; TEST_ASSERT_EQ(isatty(-1), 0); TEST_ASSERT_EQ(isatty(4096), 0); /* fd 1 may or may not be a terminal in the harness; the contract is * only that isatty answers 0 or 1 without crashing. */ TEST_ASSERT_TRUE(isatty(1) == 0 || isatty(1) == 1); rc = ttyname_r(1, name, sizeof(name)); TEST_ASSERT_TRUE(rc == 0 || rc == ENOTTY || rc == ERANGE); if (rc == 0) { TEST_ASSERT_TRUE(name[0] == '/'); } t = ttyname(1); TEST_ASSERT_TRUE(t == NULL || t[0] == '/'); return 0; } static int test_getopt_basic(void) { char *av[] = {"prog", "-a", "-b", "rest", NULL}; uopt_reset(); TEST_ASSERT_EQ(getopt(4, av, "ab"), 'a'); TEST_ASSERT_EQ(getopt(4, av, "ab"), 'b'); TEST_ASSERT_EQ(getopt(4, av, "ab"), -1); TEST_ASSERT_EQ(optind, 3); /* optind sits on the first non-option */ TEST_ASSERT_TRUE(optarg == NULL); return 0; } static int test_getopt_bundled(void) { char *av[] = {"prog", "-ab", "tail", NULL}; uopt_reset(); TEST_ASSERT_EQ(getopt(3, av, "ab"), 'a'); TEST_ASSERT_EQ(getopt(3, av, "ab"), 'b'); TEST_ASSERT_EQ(getopt(3, av, "ab"), -1); TEST_ASSERT_EQ(optind, 2); /* the first non-option is "tail" */ return 0; } static int test_getopt_args(void) { char *av1[] = {"prog", "-barg", "tail", NULL}; char *av2[] = {"prog", "-b", "arg", "tail", NULL}; /* Attached argument: "-barg" gives optarg "arg" from the same * element. */ uopt_reset(); TEST_ASSERT_EQ(getopt(3, av1, "b:"), 'b'); TEST_ASSERT_STREQ(optarg, "arg"); TEST_ASSERT_EQ(getopt(3, av1, "b:"), -1); /* Separate argument: "-b arg". */ uopt_reset(); TEST_ASSERT_EQ(getopt(4, av2, "b:"), 'b'); TEST_ASSERT_STREQ(optarg, "arg"); TEST_ASSERT_EQ(getopt(4, av2, "b:"), -1); TEST_ASSERT_EQ(optind, 3); return 0; } static int test_getopt_errors(void) { char *av1[] = {"prog", "-b", NULL}; char *av2[] = {"prog", "-z", NULL}; char *av3[] = {"prog", "-azb", NULL}; /* Missing argument with a leading ':' returns ':' silently. */ uopt_reset(); TEST_ASSERT_EQ(getopt(2, av1, ":b:"), ':'); TEST_ASSERT_EQ(optopt, 'b'); TEST_ASSERT_EQ(getopt(2, av1, ":b:"), -1); /* Unknown option returns '?', optopt carries the character. */ uopt_reset(); TEST_ASSERT_EQ(getopt(2, av2, "ab"), '?'); TEST_ASSERT_EQ(optopt, 'z'); TEST_ASSERT_EQ(getopt(2, av2, "ab"), -1); /* An unknown option in the middle of a bundle does not stop the * scan: the remaining letters are still returned. */ uopt_reset(); TEST_ASSERT_EQ(getopt(2, av3, "ab"), 'a'); TEST_ASSERT_EQ(getopt(2, av3, "ab"), '?'); TEST_ASSERT_EQ(optopt, 'z'); TEST_ASSERT_EQ(getopt(2, av3, "ab"), 'b'); TEST_ASSERT_EQ(getopt(2, av3, "ab"), -1); return 0; } static int test_getopt_dashdash(void) { char *av[] = {"prog", "-a", "--", "-b", NULL}; uopt_reset(); TEST_ASSERT_EQ(getopt(4, av, "ab"), 'a'); TEST_ASSERT_EQ(getopt(4, av, "ab"), -1); TEST_ASSERT_EQ(optind, 3); /* "--" consumed; "-b" is the first non-option */ return 0; } static int test_basename(void) { char s1[] = "/usr/lib"; char s2[] = "/usr/lib/"; char s3[] = "/"; char s4[] = "//"; char s5[] = "usr"; char s6[] = ""; char s7[] = "/usr"; TEST_ASSERT_STREQ(basename(s1), "lib"); TEST_ASSERT_STREQ(basename(s2), "lib"); TEST_ASSERT_STREQ(basename(s3), "/"); TEST_ASSERT_STREQ(basename(s4), "/"); TEST_ASSERT_STREQ(basename(s5), "usr"); TEST_ASSERT_STREQ(basename(s6), "."); TEST_ASSERT_STREQ(basename(s7), "usr"); return 0; } static int test_dirname(void) { char s1[] = "/usr/lib"; char s2[] = "/usr/lib/"; char s3[] = "/"; char s4[] = "//"; char s5[] = "usr"; char s6[] = ""; char s7[] = "/usr"; char s8[] = "usr/"; TEST_ASSERT_STREQ(dirname(s1), "/usr"); TEST_ASSERT_STREQ(dirname(s2), "/usr"); TEST_ASSERT_STREQ(dirname(s3), "/"); TEST_ASSERT_STREQ(dirname(s4), "/"); TEST_ASSERT_STREQ(dirname(s5), "."); TEST_ASSERT_STREQ(dirname(s6), "."); TEST_ASSERT_STREQ(dirname(s7), "/"); TEST_ASSERT_STREQ(dirname(s8), "."); return 0; } static int test_confstr(void) { char buf[128]; char small[8]; size_t n; n = confstr(_CS_PATH, NULL, 0); TEST_ASSERT_TRUE(n > 1); TEST_ASSERT_EQ(confstr(_CS_PATH, buf, sizeof(buf)), n); TEST_ASSERT_TRUE(buf[0] == '/'); TEST_ASSERT_TRUE(buf[n - 1] == '\0'); /* A too-small buffer is left alone but the same length is reported. */ TEST_ASSERT_EQ(confstr(_CS_PATH, small, 2), n); /* Not glibc: the version keys are the empty string. */ TEST_ASSERT_EQ(confstr(_CS_GNU_LIBC_VERSION, buf, sizeof(buf)), 1); TEST_ASSERT_STREQ(buf, ""); return 0; } static int test_realpath(void) { char here[4096]; char in[4096]; char out[4096]; char parent[4096]; char *dyn; char *slash; TEST_ASSERT_TRUE(realpath(".", out) == out); TEST_ASSERT_TRUE(getcwd(here, sizeof(here)) == here); TEST_ASSERT_STREQ(out, here); TEST_ASSERT_STREQ(realpath("/", out), "/"); /* "/proc/self/cwd" is a symlink to the working directory: exercises * readlink plus an absolute link target. NULL only when procfs is * absent. */ if (realpath("/proc/self/cwd", out) != NULL) { TEST_ASSERT_STREQ(out, here); } /* ".." pops one component: cwd/.. resolves to cwd's parent, found by * truncating here at its last slash (the root has no parent). The * input and resolved buffers are distinct: realpath reads the path * while it rewrites the resolved buffer. */ slash = strrchr(here, '/'); if (slash != NULL && slash != here) { size_t plen = (size_t)(slash - here); memcpy(parent, here, plen); parent[plen] = '\0'; strcpy(in, here); strcpy(in + strlen(in), "/.."); TEST_ASSERT_TRUE(realpath(in, out) == out); TEST_ASSERT_STREQ(out, parent); } dyn = realpath(".", NULL); TEST_ASSERT_TRUE(dyn != NULL); if (dyn != NULL) { TEST_ASSERT_STREQ(dyn, here); free(dyn); } return 0; } static int test_getwd_swab(void) { char wd[4096]; char here[4096]; char out[7] = {0}; static const char six[7] = "abcdef"; TEST_ASSERT_TRUE(getwd(wd) == wd); TEST_ASSERT_TRUE(getcwd(here, sizeof(here)) == here); TEST_ASSERT_STREQ(wd, here); swab(six, out, 6); TEST_ASSERT_STREQ(out, "badcfe"); return 0; } static const struct vlibc_test tests[] = { {"getcwd", test_getcwd}, {"isatty-ttyname", test_isatty_ttyname}, {"getopt-basic", test_getopt_basic}, {"getopt-bundled", test_getopt_bundled}, {"getopt-args", test_getopt_args}, {"getopt-errors", test_getopt_errors}, {"getopt-dashdash", test_getopt_dashdash}, {"basename", test_basename}, {"dirname", test_dirname}, {"confstr", test_confstr}, {"realpath", test_realpath}, {"getwd-swab", test_getwd_swab}, }; /* * Own main (not TEST_MAIN): supports the -f failure mode and always * leaves through the raw SYS_exit_group (see the file header). */ 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') { int before = vlibc_test_failures; char out[64]; vlibc_test_say(1, "RUN failure-shapes: "); TEST_ASSERT_TRUE(realpath(NULL, out) == NULL); TEST_ASSERT_TRUE(realpath("/no/such/vlibc-xyz", out) == NULL); TEST_ASSERT_TRUE(getcwd(out, 0) == 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"); } __syscall1(SYS_exit_group, vlibc_test_failures == before ? 0 : 1); /* 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"); __syscall1(SYS_exit_group, passed == count ? 0 : 1); /* not reached */ } #else /* !VLIBC_LEVEL_GE(2) */ int main(void) { vlibc_test_say(1, "SKIP: this surface is level 2, not available here\n"); return 0; } #endif /* VLIBC_LEVEL_GE(2) */