feat(unistd): cwd/link/readlink/path ops
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* 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 <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libgen.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vlibc/internal/test.h>
|
||||
|
||||
#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) */
|
||||
Reference in New Issue
Block a user