feat(dirent): directory iteration
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#ifndef VLIBC_DIRENT_H
|
||||
#define VLIBC_DIRENT_H
|
||||
|
||||
/*
|
||||
* vlibc — <dirent.h>.
|
||||
*
|
||||
* Directory streams: opendir/fdopendir/readdir/rewinddir/closedir,
|
||||
* seekdir/telldir/dirfd (POSIX.1-2008 base) iterate a directory via the
|
||||
* SYS_getdents64 kernel ABI, exposing one entry at a time.
|
||||
*
|
||||
* Level 1 (onlyposix): the base family above.
|
||||
* Level 2 (muslmimic): scandir, alphasort (XSI) and readdir_r
|
||||
* (obsolescent). versionsort is GNU and is not
|
||||
* provided.
|
||||
*
|
||||
* struct dirent mirrors the x86_64 getdents64 record layout (verified by
|
||||
* the static assertions below): d_ino/d_off are 64-bit, d_reclen is the
|
||||
* kernel record length, d_type carries the DT_* file type, and d_name is a
|
||||
* 256-byte NUL-terminated name buffer (NAME_MAX 255 + NUL).
|
||||
*
|
||||
* DIR is an opaque handle whose layout lives in the internal header
|
||||
* src/dirent/dirent_impl.h. None of these declarations carries an intent
|
||||
* attribute: every function performs I/O with side effects and reports
|
||||
* failures through errno, so const/pure would be unsound (the same
|
||||
* rationale sys/stat.h documents for its I/O family).
|
||||
*/
|
||||
|
||||
#include <vlibc/features.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* File type values for the d_type member of struct dirent (kernel UAPI). */
|
||||
#define DT_UNKNOWN 0
|
||||
#define DT_FIFO 1
|
||||
#define DT_CHR 2
|
||||
#define DT_DIR 4
|
||||
#define DT_BLK 6
|
||||
#define DT_REG 8
|
||||
#define DT_LNK 10
|
||||
#define DT_SOCK 12
|
||||
#define DT_WHT 14
|
||||
|
||||
/*
|
||||
* One directory entry. Layout equals the x86_64 struct linux_dirent64
|
||||
* fields 0..18 verbatim; the name then follows at offset 19, stored in the
|
||||
* conventional 256-byte buffer.
|
||||
*/
|
||||
struct dirent
|
||||
{
|
||||
ino_t d_ino; /* 0: inode number */
|
||||
off_t d_off; /* 8: offset of the next entry (seek cookie) */
|
||||
unsigned short d_reclen; /* 16: length of the kernel record */
|
||||
unsigned char d_type; /* 18: DT_* file type */
|
||||
char d_name[256]; /* 19: NUL-terminated file name */
|
||||
};
|
||||
|
||||
/* Pin the layout to the x86_64 kernel ABI. */
|
||||
_Static_assert(sizeof(struct dirent) == 280, "struct dirent must match the x86_64 getdents64 layout");
|
||||
_Static_assert(offsetof(struct dirent, d_off) == 8, "d_off must sit at offset 8");
|
||||
_Static_assert(offsetof(struct dirent, d_type) == 18, "d_type must sit at offset 18");
|
||||
_Static_assert(offsetof(struct dirent, d_name) == 19, "d_name must sit at offset 19");
|
||||
|
||||
/*
|
||||
* Opaque directory stream handle. The struct tag stays usable from the
|
||||
* internal layout header, which defines struct vlibc_DIR (see
|
||||
* src/dirent/dirent_impl.h).
|
||||
*/
|
||||
typedef struct vlibc_DIR DIR;
|
||||
|
||||
/*
|
||||
* Open the directory named by path for reading and return a directory
|
||||
* stream positioned at its first entry, or NULL with errno set (a
|
||||
* non-directory path fails with ENOTDIR). The stream owns a descriptor
|
||||
* that closedir() releases.
|
||||
*/
|
||||
DIR *
|
||||
opendir(const char *path);
|
||||
|
||||
/*
|
||||
* Like opendir(), but over the already-open descriptor fd, which must
|
||||
* refer to a directory (validated with fstat). On failure NULL is returned
|
||||
* with errno set and fd is left open and owned by the caller.
|
||||
*/
|
||||
DIR *
|
||||
fdopendir(int fd);
|
||||
|
||||
/*
|
||||
* Return the next directory entry of dir, or NULL at the end of the
|
||||
* directory (errno untouched) or on error (errno set). The result points
|
||||
* at storage owned by dir and is valid until the next call to readdir,
|
||||
* rewinddir, seekdir, or closedir on the same stream.
|
||||
*/
|
||||
struct dirent *
|
||||
readdir(DIR *dir);
|
||||
|
||||
/*
|
||||
* Reset dir to the beginning of the directory: the next readdir returns
|
||||
* the first entry again. Never fails.
|
||||
*/
|
||||
void
|
||||
rewinddir(DIR *dir);
|
||||
|
||||
/*
|
||||
* Close dir, releasing its descriptor and storage. Return 0, or -1 with
|
||||
* errno set if the underlying close fails.
|
||||
*/
|
||||
int
|
||||
closedir(DIR *dir);
|
||||
|
||||
/*
|
||||
* Reposition dir so the next readdir resumes at the location loc, which
|
||||
* must be a value previously returned by telldir (a getdents64 seek
|
||||
* cookie). Never fails.
|
||||
*/
|
||||
void
|
||||
seekdir(DIR *dir, long loc);
|
||||
|
||||
/*
|
||||
* Return the current location of dir, for a later seekdir. The location is
|
||||
* the point after the entry most recently returned by readdir; it becomes
|
||||
* indeterminate after rewinddir or closedir.
|
||||
*/
|
||||
long
|
||||
telldir(DIR *dir);
|
||||
|
||||
/*
|
||||
* Return the descriptor underlying dir. The descriptor stays owned by the
|
||||
* stream and remains valid until closedir.
|
||||
*/
|
||||
int
|
||||
dirfd(DIR *dir);
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/* Level 2 (muslmimic): XSI and obsolescent. */
|
||||
|
||||
/*
|
||||
* Reentrant readdir: store the next entry in *buf and set *result to buf;
|
||||
* at the end of the directory *result is NULL. Returns 0 at end of
|
||||
* directory, an error number on failure (errno is not used for the error
|
||||
* report), and leaves errno unmodified on success. Obsolescent.
|
||||
*/
|
||||
int
|
||||
readdir_r(DIR *restrict dir, struct dirent *restrict buf,
|
||||
struct dirent **restrict result);
|
||||
|
||||
/*
|
||||
* Read the whole directory named by path and store a malloc'd array of
|
||||
* malloc'd struct dirent copies in *res (both released with free; the
|
||||
* array is NULL-terminated with one extra NULL pointer). Only entries for
|
||||
* which sel is NULL or returns nonzero are kept; cmp, when non-NULL, sorts
|
||||
* the array (alphasort is the strcmp-on-name comparator). Returns the
|
||||
* number of entries, or -1 with errno set. XSI.
|
||||
*/
|
||||
int
|
||||
scandir(const char *path, struct dirent ***res,
|
||||
int (*sel)(const struct dirent *),
|
||||
int (*cmp)(const struct dirent **, const struct dirent **));
|
||||
|
||||
/*
|
||||
* Lexicographic comparator over the d_name fields of two struct dirent
|
||||
* pointers, for use as scandir's cmp argument. XSI.
|
||||
*/
|
||||
int
|
||||
alphasort(const struct dirent **a, const struct dirent **b);
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VLIBC_DIRENT_H */
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* alphasort (todo 24, XSI): the scandir comparator that orders entries by
|
||||
* strcmp on their names. Receives two pointers to the array's struct
|
||||
* dirent pointers.
|
||||
*/
|
||||
int
|
||||
alphasort(const struct dirent **a, const struct dirent **b)
|
||||
{
|
||||
return strcmp((*a)->d_name, (*b)->d_name);
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include "../internal/malloc.h"
|
||||
#include "../internal/syscall.h"
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* closedir (todo 24): release the descriptor and the stream storage. The
|
||||
* descriptor is closed first so a close failure still reports -1, but the
|
||||
* stream is freed either way (its errno is preserved across the free).
|
||||
*/
|
||||
int
|
||||
closedir(DIR *dir)
|
||||
{
|
||||
struct vlibc_DIR *d = dir;
|
||||
int fd = d->fd;
|
||||
int r = syscall_ret(__syscall1(SYS_close, fd));
|
||||
|
||||
__libc_free(d);
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef VLIBC_DIRENT_INTERNAL_H
|
||||
#define VLIBC_DIRENT_INTERNAL_H
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* vlibc — internal directory-stream state (todo 24).
|
||||
*
|
||||
* struct vlibc_DIR is the completion of the opaque DIR handle from
|
||||
* <dirent.h>. readdir parses raw getdents64 records out of buf; the kernel
|
||||
* fills buf with whole records and advances its own per-fd directory
|
||||
* offset, so sequential iteration needs no lseek. seekdir/telldir hand the
|
||||
* kernel's per-record d_off cookie (the offset of the NEXT entry) back and
|
||||
* forth via lseek: d->de.d_off therefore always holds the resume point
|
||||
* after the entry most recently returned by readdir.
|
||||
*/
|
||||
#define VLIBC_DIRENT_BUFSZ 2048
|
||||
|
||||
struct vlibc_DIR
|
||||
{
|
||||
int fd; /* directory descriptor, owned by the stream */
|
||||
size_t buf_pos; /* next unparsed byte within buf */
|
||||
size_t buf_end; /* first unused byte of buf */
|
||||
struct dirent de; /* storage for the entry readdir returns */
|
||||
char buf[VLIBC_DIRENT_BUFSZ];
|
||||
};
|
||||
|
||||
/*
|
||||
* The kernel-side record produced by SYS_getdents64, transcribed as the
|
||||
* x86_64 linux_dirent64 layout: ino at 0, off at 8, reclen at 16, type at
|
||||
* 18, name at 19. The 256-byte name array makes the struct exactly the
|
||||
* size of a maximal record: 19 + (255-name + NUL) = 275, padded by struct
|
||||
* alignment to 280, which is also the largest d_reclen the kernel emits
|
||||
* (every record length is rounded up to a multiple of 8). readdir memcpy's
|
||||
* a whole record into this struct before reading the fields, so no
|
||||
* unaligned or aliasing access ever happens.
|
||||
*/
|
||||
struct vlibc_linux_dirent64
|
||||
{
|
||||
ino_t d_ino;
|
||||
off_t d_off;
|
||||
unsigned short d_reclen;
|
||||
unsigned char d_type;
|
||||
char d_name[256];
|
||||
};
|
||||
|
||||
_Static_assert(sizeof(struct vlibc_linux_dirent64) == 280,
|
||||
"linux_dirent64 with a 256-byte name must be 280 bytes");
|
||||
_Static_assert(offsetof(struct vlibc_linux_dirent64, d_name) == 19,
|
||||
"linux_dirent64 name must sit at offset 19");
|
||||
_Static_assert(offsetof(struct dirent, d_name) ==
|
||||
offsetof(struct vlibc_linux_dirent64, d_name),
|
||||
"dirent and linux_dirent64 must share the name offset");
|
||||
|
||||
#endif /* VLIBC_DIRENT_INTERNAL_H */
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* dirfd (todo 24): return the descriptor the stream reads through. The
|
||||
* descriptor stays owned by the stream; closedir closes it.
|
||||
*/
|
||||
int
|
||||
dirfd(DIR *dir)
|
||||
{
|
||||
struct vlibc_DIR *d = dir;
|
||||
|
||||
return d->fd;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../internal/malloc.h"
|
||||
#include "../internal/syscall.h"
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* fdopendir (todo 24): validate the descriptor with SYS_fstat (POSIX
|
||||
* requires the fd to name a directory), then allocate the stream state.
|
||||
* On failure the descriptor is NOT closed — it stays owned by the caller,
|
||||
* exactly as passed in.
|
||||
*/
|
||||
DIR *
|
||||
fdopendir(int fd)
|
||||
{
|
||||
struct stat st;
|
||||
DIR *d;
|
||||
|
||||
if (syscall_ret(__syscall2(SYS_fstat, fd, (long)&st)) < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (!S_ISDIR(st.st_mode))
|
||||
{
|
||||
errno = ENOTDIR;
|
||||
return NULL;
|
||||
}
|
||||
d = __libc_malloc(sizeof *d);
|
||||
if (d == NULL)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
d->fd = fd;
|
||||
d->buf_pos = 0;
|
||||
d->buf_end = 0;
|
||||
d->de.d_off = 0;
|
||||
return d;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* opendir (todo 24): open the directory via SYS_openat with O_DIRECTORY so
|
||||
* the kernel rejects non-directories with ENOTDIR before any stream state
|
||||
* exists, then hand the descriptor to fdopendir (which re-validates with
|
||||
* fstat). On fdopendir failure the descriptor is closed again: opendir
|
||||
* never leaks.
|
||||
*/
|
||||
DIR *
|
||||
opendir(const char *path)
|
||||
{
|
||||
int fd = syscall_ret(
|
||||
__syscall3(SYS_openat, AT_FDCWD, (long)path, O_RDONLY | O_DIRECTORY));
|
||||
DIR *d;
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
d = fdopendir(fd);
|
||||
if (d == NULL)
|
||||
{
|
||||
syscall_ret(__syscall1(SYS_close, fd));
|
||||
return NULL;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* readdir (todo 24): expose the entries the kernel wrote into the stream
|
||||
* buffer by SYS_getdents64, one per call. When the buffer is exhausted a
|
||||
* fresh getdents64 continues at the kernel's per-fd directory offset (no
|
||||
* lseek needed for sequential iteration). A zero-length result is end of
|
||||
* directory: NULL is returned with errno untouched. Each record is copied
|
||||
* into the stream's own struct dirent, so the returned pointer stays valid
|
||||
* until the next call on the same stream.
|
||||
*/
|
||||
struct dirent *
|
||||
readdir(DIR *dir)
|
||||
{
|
||||
struct vlibc_DIR *d = dir;
|
||||
struct vlibc_linux_dirent64 k;
|
||||
unsigned short reclen;
|
||||
size_t namelen;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (d->buf_pos >= d->buf_end)
|
||||
{
|
||||
long r =
|
||||
__syscall3(SYS_getdents64, d->fd, (long)d->buf,
|
||||
(long)sizeof(d->buf));
|
||||
|
||||
if (r <= 0)
|
||||
{
|
||||
syscall_ret(r);
|
||||
return NULL;
|
||||
}
|
||||
d->buf_pos = 0;
|
||||
d->buf_end = (size_t)r;
|
||||
}
|
||||
|
||||
if (d->buf_end - d->buf_pos < offsetof(struct vlibc_linux_dirent64, d_name))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
memcpy(&reclen,
|
||||
d->buf + d->buf_pos + offsetof(struct vlibc_linux_dirent64, d_reclen),
|
||||
sizeof reclen);
|
||||
if (reclen < offsetof(struct vlibc_linux_dirent64, d_name) + 2 ||
|
||||
reclen > sizeof k)
|
||||
{
|
||||
/* The kernel always emits whole well-formed records; a broken
|
||||
* length would otherwise walk off the buffer. Stop the scan. */
|
||||
return NULL;
|
||||
}
|
||||
memcpy(&k, d->buf + d->buf_pos, reclen);
|
||||
d->buf_pos += reclen;
|
||||
|
||||
d->de.d_ino = k.d_ino;
|
||||
d->de.d_off = k.d_off;
|
||||
d->de.d_reclen = reclen;
|
||||
d->de.d_type = k.d_type;
|
||||
namelen = (size_t)reclen - offsetof(struct vlibc_linux_dirent64, d_name);
|
||||
if (namelen >= sizeof(d->de.d_name))
|
||||
{
|
||||
namelen = sizeof(d->de.d_name) - 1;
|
||||
}
|
||||
memcpy(d->de.d_name, k.d_name, namelen);
|
||||
d->de.d_name[namelen] = '\0';
|
||||
return &d->de;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* readdir_r (todo 24, obsolescent): reentrant readdir. The result is
|
||||
* copied into the caller's buf and *result points at it; at end of
|
||||
* directory *result is NULL. Errors are reported as the return value
|
||||
* (never through errno), and errno is preserved across successful calls.
|
||||
* readdir_r is distinguished from end-of-directory by checking whether
|
||||
* readdir moved errno.
|
||||
*/
|
||||
int
|
||||
readdir_r(DIR *restrict dir, struct dirent *restrict buf,
|
||||
struct dirent **restrict result)
|
||||
{
|
||||
struct dirent *de;
|
||||
int saved = errno;
|
||||
|
||||
errno = 0;
|
||||
de = readdir(dir);
|
||||
if (de != NULL)
|
||||
{
|
||||
memcpy(buf, de, sizeof *buf);
|
||||
*result = buf;
|
||||
errno = saved;
|
||||
return 0;
|
||||
}
|
||||
if (errno != 0)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
errno = saved;
|
||||
*result = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* rewinddir (todo 24): return the stream to the start of the directory.
|
||||
* SYS_lseek back to offset 0 resets the kernel's per-fd directory
|
||||
* position; the cached records (if any) are dropped and the stored seek
|
||||
* cookie is reset, so the next readdir refills from the first entry.
|
||||
* rewinddir has no error return; a failed lseek leaves the stream
|
||||
* positioned wherever the kernel is.
|
||||
*/
|
||||
void
|
||||
rewinddir(DIR *dir)
|
||||
{
|
||||
struct vlibc_DIR *d = dir;
|
||||
|
||||
syscall_ret(__syscall3(SYS_lseek, d->fd, 0, SEEK_SET));
|
||||
d->buf_pos = 0;
|
||||
d->buf_end = 0;
|
||||
d->de.d_off = 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../internal/malloc.h"
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* scandir (todo 24, XSI): walk the whole directory and hand back a sorted,
|
||||
* NULL-terminated array of heap copies. Entries are copied into
|
||||
* minimal-size blocks (header fields plus the name), all released with the
|
||||
* public free(); the array itself is a malloc'd growable buffer resized
|
||||
* with realloc. "." and ".." are reported like any other entry.
|
||||
*/
|
||||
|
||||
/* Copy one entry into minimal heap storage sized for its name. */
|
||||
static struct dirent *
|
||||
dirent_dup(const struct dirent *de)
|
||||
{
|
||||
size_t nlen = strlen(de->d_name) + 1;
|
||||
struct dirent *nd =
|
||||
__libc_malloc(offsetof(struct dirent, d_name) + nlen);
|
||||
|
||||
if (nd == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
memcpy(nd, de, offsetof(struct dirent, d_name));
|
||||
memcpy(nd->d_name, de->d_name, nlen);
|
||||
return nd;
|
||||
}
|
||||
|
||||
int
|
||||
scandir(const char *path, struct dirent ***res,
|
||||
int (*sel)(const struct dirent *),
|
||||
int (*cmp)(const struct dirent **, const struct dirent **))
|
||||
{
|
||||
DIR *d;
|
||||
struct dirent *de;
|
||||
struct dirent **names = NULL;
|
||||
size_t cnt = 0;
|
||||
size_t cap = 0;
|
||||
|
||||
d = opendir(path);
|
||||
if (d == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
while ((de = readdir(d)) != NULL)
|
||||
{
|
||||
if (sel != NULL && !sel(de))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (cnt == cap)
|
||||
{
|
||||
size_t ncap = cap == 0 ? 8 : cap * 2;
|
||||
|
||||
if (ncap <= cap || ncap > (size_t)-1 / sizeof *names)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
names = realloc(names, ncap * sizeof *names);
|
||||
if (names == NULL)
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
cap = ncap;
|
||||
}
|
||||
names[cnt] = dirent_dup(de);
|
||||
if (names[cnt] == NULL)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
if (cmp != NULL)
|
||||
{
|
||||
qsort(names, cnt, sizeof *names,
|
||||
(int (*)(const void *, const void *))cmp);
|
||||
}
|
||||
names = realloc(names, (cnt + 1) * sizeof *names);
|
||||
if (names == NULL)
|
||||
{
|
||||
goto fail_nofree;
|
||||
}
|
||||
names[cnt] = NULL;
|
||||
*res = names;
|
||||
return (int)cnt;
|
||||
|
||||
fail:
|
||||
while (cnt > 0)
|
||||
{
|
||||
__libc_free(names[--cnt]);
|
||||
}
|
||||
__libc_free(names);
|
||||
closedir(d);
|
||||
return -1;
|
||||
|
||||
fail_nofree:
|
||||
while (cnt > 0)
|
||||
{
|
||||
__libc_free(names[--cnt]);
|
||||
}
|
||||
__libc_free(names);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* seekdir (todo 24): reposition the stream to the directory location loc
|
||||
* (a d_off cookie previously returned by telldir). SYS_lseek to that
|
||||
* cookie makes the next SYS_getdents64 resume at the entry the cookie
|
||||
* denotes; any records cached ahead of the seek point are dropped. The
|
||||
* kernel accepts exactly the cookies getdents64 hands out, which is all
|
||||
* telldir ever returns. seekdir has no error return; on a failed lseek
|
||||
* the buffered records are kept so reading continues where it left off.
|
||||
*/
|
||||
void
|
||||
seekdir(DIR *dir, long loc)
|
||||
{
|
||||
struct vlibc_DIR *d = dir;
|
||||
|
||||
if (syscall_ret(__syscall3(SYS_lseek, d->fd, (long)loc, SEEK_SET)) >= 0)
|
||||
{
|
||||
d->buf_pos = 0;
|
||||
d->buf_end = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include "dirent_impl.h"
|
||||
|
||||
/*
|
||||
* telldir (todo 24): return the stream's current directory location.
|
||||
* readdir stores the kernel's d_off cookie of every entry it hands out —
|
||||
* the offset at which iteration resumes AFTER that entry — so the cookie
|
||||
* of the most recently returned entry is exactly the location a matching
|
||||
* seekdir must restore. A fresh or rewound stream reports 0, the start.
|
||||
*/
|
||||
long
|
||||
telldir(DIR *dir)
|
||||
{
|
||||
struct vlibc_DIR *d = dir;
|
||||
|
||||
return (long)d->de.d_off;
|
||||
}
|
||||
@@ -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