feat(dirent): directory iteration
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* vlibc — dirent test (todo 24).
|
||||
*
|
||||
* Exercises the dirent.h surface end to end against a scratch directory
|
||||
* created with raw syscalls:
|
||||
*
|
||||
* 1. opendir on the temp dir lists exactly . .. alpha beta gamma; every
|
||||
* entry carries a nonzero d_ino; . reports d_type DT_DIR and alpha
|
||||
* DT_REG.
|
||||
* 2. readdir past the end returns NULL; a repeated readdir stays NULL.
|
||||
* 3. rewinddir re-lists the same five entries.
|
||||
* 4. dirfd returns a usable descriptor; fdopendir succeeds on a raw
|
||||
* directory descriptor.
|
||||
* 5. Level 2: scandir + alphasort return the five entries sorted
|
||||
* (. .. alpha beta gamma), NULL-terminated, with d_type copied, and
|
||||
* the caller can free() every element and the array.
|
||||
*
|
||||
* The -f mode covers the failure paths, all asserted on return values
|
||||
* only: opendir of a nonexistent directory returns NULL, fdopendir of a
|
||||
* regular-file descriptor returns NULL, and a seekdir back to a saved
|
||||
* telldir location resumes at the entry that originally followed it. The
|
||||
* library writes errno on these paths, so -f exits through raw
|
||||
* SYS_exit_group (house pattern); the default mode never triggers an
|
||||
* errno-writing library path.
|
||||
*
|
||||
* All diagnostics go through raw SYS_write (no stdio); the test reads no
|
||||
* host headers — under -Iinclude the vlibc public headers shadow GCC's
|
||||
* internal ones. Not part of the library proper; compiled manually for
|
||||
* this todo (the tests/ + make check wiring is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
#define DIRPATH "/tmp/vlibc-t24"
|
||||
|
||||
static const char *const FILES[3] = {
|
||||
DIRPATH "/alpha",
|
||||
DIRPATH "/beta",
|
||||
DIRPATH "/gamma",
|
||||
};
|
||||
|
||||
static int failures;
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. The
|
||||
* optimize attribute keeps GCC from lowering the length loop into a
|
||||
* strlen call, which would leave a vlibc-owned symbol undefined in this
|
||||
* host-linked standalone binary (house idiom, see src/string). */
|
||||
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
/* Write v in decimal to fd. */
|
||||
static void
|
||||
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
char buf[24];
|
||||
int i = (int)sizeof(buf);
|
||||
|
||||
buf[--i] = '\0';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Drop any leftover scratch dir (files, then the dir itself); errors are
|
||||
* fine when nothing exists yet. */
|
||||
static void
|
||||
rm_tree(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
__syscall3(SYS_unlinkat, AT_FDCWD, (long)FILES[i], 0);
|
||||
}
|
||||
__syscall3(SYS_unlinkat, AT_FDCWD, (long)DIRPATH, AT_REMOVEDIR);
|
||||
}
|
||||
|
||||
/* Create the scratch directory and its three one-byte files. */
|
||||
static int
|
||||
make_dir(void)
|
||||
{
|
||||
int fd;
|
||||
int i;
|
||||
|
||||
rm_tree();
|
||||
if ((int)__syscall3(SYS_mkdirat, AT_FDCWD, (long)DIRPATH, 0700) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
fd = (int)__syscall4(SYS_openat, AT_FDCWD, (long)FILES[i],
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
if (fd < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)"x", 1);
|
||||
__syscall1(SYS_close, fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A full listing captured as copied names. */
|
||||
struct listing
|
||||
{
|
||||
char names[8][64];
|
||||
int count;
|
||||
};
|
||||
|
||||
/* Drain dir, copying each entry name into ls. */
|
||||
static void
|
||||
fill_listing(DIR *d, struct listing *ls)
|
||||
{
|
||||
struct dirent *e;
|
||||
int n = 0;
|
||||
|
||||
while ((e = readdir(d)) != NULL)
|
||||
{
|
||||
if (n < 8)
|
||||
{
|
||||
strcpy(ls->names[n], e->d_name);
|
||||
}
|
||||
n++;
|
||||
}
|
||||
ls->count = n;
|
||||
}
|
||||
|
||||
static int
|
||||
has_name(const struct listing *ls, const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ls->count; i++)
|
||||
{
|
||||
if (strcmp(ls->names[i], name) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
same_sets(const struct listing *x, const struct listing *y)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (x->count != y->count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < x->count; i++)
|
||||
{
|
||||
if (!has_name(y, x->names[i]))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Scenarios 1-4: iteration, exhaustion, rewind, dirfd/fdopendir. */
|
||||
static void
|
||||
basic_scenarios(void)
|
||||
{
|
||||
struct listing first;
|
||||
struct listing second;
|
||||
DIR *d;
|
||||
struct dirent *e;
|
||||
int n = 0;
|
||||
|
||||
d = opendir(DIRPATH);
|
||||
check(d != NULL, "opendir on the temp directory succeeds");
|
||||
if (d == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
check(dirfd(d) >= 0, "dirfd on the open stream returns a valid descriptor");
|
||||
|
||||
first.count = 0;
|
||||
while ((e = readdir(d)) != NULL)
|
||||
{
|
||||
check(e->d_ino != 0, "every entry reports a nonzero d_ino");
|
||||
if (strcmp(e->d_name, ".") == 0)
|
||||
{
|
||||
check(e->d_type == DT_DIR, "the . entry carries d_type DT_DIR");
|
||||
}
|
||||
if (strcmp(e->d_name, "alpha") == 0)
|
||||
{
|
||||
check(e->d_type == DT_REG, "the alpha file carries d_type DT_REG");
|
||||
}
|
||||
if (n < 8)
|
||||
{
|
||||
strcpy(first.names[n], e->d_name);
|
||||
}
|
||||
n++;
|
||||
}
|
||||
first.count = n;
|
||||
check(first.count == 5, "the first pass lists exactly 5 entries");
|
||||
check(readdir(d) == NULL, "readdir at end of directory returns NULL");
|
||||
check(readdir(d) == NULL, "a further readdir at end still returns NULL");
|
||||
check(has_name(&first, ".") && has_name(&first, "..") &&
|
||||
has_name(&first, "alpha") && has_name(&first, "beta") &&
|
||||
has_name(&first, "gamma"),
|
||||
"the first pass sees . .. alpha beta gamma");
|
||||
|
||||
rewinddir(d);
|
||||
fill_listing(d, &second);
|
||||
check(second.count == 5, "after rewinddir the second pass lists 5 entries again");
|
||||
check(same_sets(&first, &second), "the rewound listing matches the first listing");
|
||||
check(closedir(d) == 0, "closedir returns 0");
|
||||
|
||||
{
|
||||
int fd = (int)__syscall3(SYS_openat, AT_FDCWD, (long)DIRPATH,
|
||||
O_RDONLY | O_DIRECTORY);
|
||||
DIR *dd;
|
||||
|
||||
check(fd >= 0, "raw open of the directory succeeds");
|
||||
if (fd < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
dd = fdopendir(fd);
|
||||
check(dd != NULL, "fdopendir on a directory descriptor succeeds");
|
||||
if (dd != NULL)
|
||||
{
|
||||
check(closedir(dd) == 0, "closedir after fdopendir returns 0");
|
||||
}
|
||||
else
|
||||
{
|
||||
__syscall1(SYS_close, fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/* Scenario 5 (level 2): scandir + alphasort. */
|
||||
static void
|
||||
scandir_scenario(void)
|
||||
{
|
||||
struct dirent **names = NULL;
|
||||
int n;
|
||||
int i;
|
||||
|
||||
n = scandir(DIRPATH, &names, NULL, alphasort);
|
||||
check(n == 5, "scandir returns the 5 entries");
|
||||
if (names == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
check(names[n] == NULL, "the scandir array is NULL-terminated");
|
||||
check(strcmp(names[0]->d_name, ".") == 0 && strcmp(names[1]->d_name, "..") == 0,
|
||||
"alphasort puts . and .. first in that order");
|
||||
check(strcmp(names[2]->d_name, "alpha") == 0 &&
|
||||
strcmp(names[3]->d_name, "beta") == 0 &&
|
||||
strcmp(names[4]->d_name, "gamma") == 0,
|
||||
"alphasort orders the files alpha beta gamma");
|
||||
check(names[2]->d_type == DT_REG, "scandir copies d_type into its entries");
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
free(names[i]);
|
||||
}
|
||||
free(names);
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/*
|
||||
* Failure scenarios (-f): every assertion is on the return value only.
|
||||
* opendir/fdopendir write errno on these paths (host-TCB hazard), so the
|
||||
* process exits through raw SYS_exit_group.
|
||||
*/
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
struct dirent *e1;
|
||||
struct dirent *e2;
|
||||
struct dirent *e3;
|
||||
int rc = 0;
|
||||
int fdf;
|
||||
long loc;
|
||||
DIR *d;
|
||||
|
||||
if (make_dir() != 0)
|
||||
{
|
||||
say(2, "FAIL: cannot set up the temp directory\n");
|
||||
return 1;
|
||||
}
|
||||
if (opendir("/nonexistent/vlibc-dir") != NULL)
|
||||
{
|
||||
say(2, "FAIL: opendir on a nonexistent directory did not return NULL\n");
|
||||
rc = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "PASS: opendir on a nonexistent directory -> NULL\n");
|
||||
}
|
||||
|
||||
fdf = (int)__syscall3(SYS_openat, AT_FDCWD, (long)FILES[0], O_RDONLY);
|
||||
if (fdf >= 0)
|
||||
{
|
||||
d = fdopendir(fdf);
|
||||
if (d != NULL)
|
||||
{
|
||||
say(2, "FAIL: fdopendir on a regular-file descriptor did not return NULL\n");
|
||||
rc = 1;
|
||||
closedir(d);
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "PASS: fdopendir on a regular-file descriptor -> NULL\n");
|
||||
__syscall1(SYS_close, fdf);
|
||||
}
|
||||
}
|
||||
|
||||
d = opendir(DIRPATH);
|
||||
if (d == NULL)
|
||||
{
|
||||
say(2, "FAIL: opendir failed for the seekdir round trip\n");
|
||||
rc = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
e1 = readdir(d);
|
||||
loc = telldir(d);
|
||||
e2 = readdir(d);
|
||||
if (e1 == NULL || e2 == NULL)
|
||||
{
|
||||
say(2, "FAIL: not enough entries for the seekdir round trip\n");
|
||||
rc = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
seekdir(d, loc);
|
||||
e3 = readdir(d);
|
||||
if (e3 != NULL && strcmp(e3->d_name, e2->d_name) == 0)
|
||||
{
|
||||
say(1, "PASS: seekdir back to a telldir location resumes at the same entry\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: seekdir did not resume at the telldir location\n");
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
rm_tree();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
int rc = failure_scenarios();
|
||||
|
||||
__syscall1(SYS_exit_group, rc);
|
||||
return rc; /* not reached */
|
||||
}
|
||||
|
||||
if (make_dir() != 0)
|
||||
{
|
||||
say(2, "FAIL: cannot set up the temp directory\n");
|
||||
return 1;
|
||||
}
|
||||
basic_scenarios();
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
scandir_scenario();
|
||||
#endif
|
||||
rm_tree();
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all dirent tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user