/* * 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 #include #include #include #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; }