Skip to content

Commit

Permalink
lib/vfscore: Add non-largefile variant for dirent
Browse files Browse the repository at this point in the history
Unikraft defines the `dirent` structure as identical to `dirent64`.
This works fine if not using Unikraft in binary compatibility mode,
since we have total control over the internal functions/structures that
are used.

However, if we use Unikraft in binary compatibility mode, we want to use
Linux `dirent` and `dirent64` structures in order to maintain compatibility
with older libc versions that do not use the `*64` calls by default.
Since the filesystems `READDIR` vop uses `dirent64`, we will convert to a
`dirent` structure within the `readdir_r` call.

Signed-off-by: Stefan Jumarea <stefanjumarea02@gmail.com>
GitHub-Closes: #919
Reviewed-by: Radu Nichita <radunichita99@gmail.com>
Reviewed-by: Sergiu Moga <sergiu@unikraft.io>
Reviewed-by: Simon Kuenzer <simon@unikraft.io>
Approved-by: Simon Kuenzer <simon@unikraft.io>
Tested-by: Unikraft CI <monkey@unikraft.io>
GitHub-Closes: #963
  • Loading branch information
StefanJum authored and unikraft-bot committed Aug 16, 2023
1 parent 43ee3b3 commit 2ea08f3
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 35 deletions.
2 changes: 1 addition & 1 deletion lib/9pfs/9pfs_vnops.c
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ static int uk_9pfs_rmdir(struct vnode *dvp, struct vnode *vp,
}

static int uk_9pfs_readdir(struct vnode *vp, struct vfscore_file *fp,
struct dirent *dir)
struct dirent64 *dir)
{
struct uk_9pfs_mount_data *md = UK_9PFS_MD(vp->v_mount);
struct uk_9pdev *dev = md->dev;
Expand Down
3 changes: 2 additions & 1 deletion lib/devfs/devfs_vnops.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ devfs_lookup(struct vnode *dvp, const char *name, struct vnode **vpp)
* @vp: vnode of the directory.
*/
static int
devfs_readdir(struct vnode *vp __unused, struct vfscore_file *fp, struct dirent *dir)
devfs_readdir(struct vnode *vp __unused, struct vfscore_file *fp,
struct dirent64 *dir)
{
struct devinfo info;
int error, i;
Expand Down
38 changes: 31 additions & 7 deletions lib/nolibc/include/dirent.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,42 @@ typedef struct __dirstream DIR;
#define _DIRENT_HAVE_D_OFF
#define _DIRENT_HAVE_D_TYPE

struct dirent {
/**
* The dirent and dirent64 structures must match the Linux system call ABI
* for binary compatibility.
*/
struct dirent64 {
ino_t d_ino;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256];
};

#if CONFIG_LIBVFSCORE_NONLARGEFILE
struct dirent {
unsigned long d_ino;
unsigned long d_off;
unsigned short d_reclen;
char d_name[256];
unsigned char pad;
unsigned char d_type;
};
#else /* !CONFIG_LIBVFSCORE_NONLARGEFILE */
#define dirent dirent64
#endif /* !CONFIG_LIBVFSCORE_NONLARGEFILE */

#define d_fileno d_ino

int closedir(DIR *);
DIR *fdopendir(int);
DIR *opendir(const char *);
struct dirent64 *readdir64(DIR *dir);
int readdir64_r(DIR *__restrict, struct dirent64 *__restrict,
struct dirent64 **__restrict);
struct dirent *readdir(DIR *);
int readdir_r(DIR *__restrict, struct dirent *__restrict, struct dirent **__restrict);

void rewinddir(DIR *);
int dirfd(DIR *);

Expand All @@ -57,18 +78,21 @@ long telldir(DIR *);
#define DT_WHT 14
#define IFTODT(x) ((x)>>12 & 017)
#define DTTOIF(x) ((x)<<12)
int getdents(int, struct dirent *, size_t);
int getdents(int fd, struct dirent *dirp, size_t count);
int getdents64(int fd, struct dirent64 *dirp, size_t count);
#endif

#ifdef _GNU_SOURCE
int versionsort(const struct dirent **, const struct dirent **);
int versionsort(const struct dirent64 **, const struct dirent64 **);
#endif

#if defined(_LARGEFILE64_SOURCE) || defined(_GNU_SOURCE)
#define dirent64 dirent
#define readdir64 readdir
#define readdir64_r readdir_r
#define scandir64 scandir

/**
* `alphasort()` and `versionsort()` are not provided in the Unikraft core,
* so we can just leave the largefile functions as aliases to the non largefile
* definitions
*/
#define alphasort64 alphasort
#define versionsort64 versionsort
#define off64_t off_t
Expand Down
2 changes: 1 addition & 1 deletion lib/ramfs/ramfs_vnops.c
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ ramfs_rename(struct vnode *dvp1, struct vnode *vp1, const char *name1 __unused,
* @vp: vnode of the directory.
*/
static int
ramfs_readdir(struct vnode *vp, struct vfscore_file *fp, struct dirent *dir)
ramfs_readdir(struct vnode *vp, struct vfscore_file *fp, struct dirent64 *dir)
{
struct ramfs_node *np, *dnp;
int i;
Expand Down
13 changes: 13 additions & 0 deletions lib/vfscore/Config.uk
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ config LIBVFSCORE_PIPE_SIZE_ORDER
help
The size of the internal buffer for anonymous pipes is 2^order.

config LIBVFSCORE_NONLARGEFILE
bool "Non-largefile system calls"
default y if LIBSYSCALL_SHIM_HANDLER
default n
help
Add different dirent and dirent64 structure and their respective
syscalls (i.e. treat Linux non largefile legacy syscalls properly).
If this option is not set, the dirent structure will be aliased to
dirent64, and all other nonlargefile functions will be aliased to
their largefile variant (e.g. readdir = readdir64).
If lib/syscall_shim is enabled and this option is not selected, only
the 64-bit version of the system calls are registered.

config LIBVFSCORE_AUTOMOUNT_ROOTFS
bool "Automatically mount a root filesysytem (/)"
default n
Expand Down
2 changes: 2 additions & 0 deletions lib/vfscore/Makefile.uk
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += symlink-2
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += unlink-1
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += unlinkat-3
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += chroot-1
ifeq ($(CONFIG_LIBVFSCORE_NONLARGEFILE),y)
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += getdents-3
endif
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += getdents64-3
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += newfstatat-4
UK_PROVIDED_SYSCALLS-$(CONFIG_LIBVFSCORE) += open-3
Expand Down
3 changes: 2 additions & 1 deletion lib/vfscore/include/vfscore/vnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ typedef int (*vnop_seek_t) (struct vnode *, struct vfscore_file *,
off_t, off_t);
typedef int (*vnop_ioctl_t) (struct vnode *, struct vfscore_file *, unsigned long, void *);
typedef int (*vnop_fsync_t) (struct vnode *, struct vfscore_file *);
typedef int (*vnop_readdir_t) (struct vnode *, struct vfscore_file *, struct dirent *);
typedef int (*vnop_readdir_t) (struct vnode *, struct vfscore_file *,
struct dirent64 *);
typedef int (*vnop_lookup_t) (struct vnode *, const char *, struct vnode **);
typedef int (*vnop_create_t) (struct vnode *, const char *, mode_t);
typedef int (*vnop_remove_t) (struct vnode *, struct vnode *, const char *);
Expand Down
184 changes: 163 additions & 21 deletions lib/vfscore/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ UK_TRACEPOINT(trace_vfs_readdir, "%d %#x", int, struct dirent*);
UK_TRACEPOINT(trace_vfs_readdir_ret, "");
UK_TRACEPOINT(trace_vfs_readdir_err, "%d", int);

UK_TRACEPOINT(trace_vfs_readdir64, "%d %p", int, struct dirent64*);
UK_TRACEPOINT(trace_vfs_readdir_ret64, "");
UK_TRACEPOINT(trace_vfs_readdir_err64, "%d", int);

struct __dirstream
{
int fd;
Expand Down Expand Up @@ -1021,6 +1025,7 @@ int closedir(DIR *dir)
return 0;
}

#if CONFIG_LIBVFSCORE_NONLARGEFILE
int scandir(const char *path, struct dirent ***res,
int (*sel)(const struct dirent *),
int (*cmp)(const struct dirent **, const struct dirent **))
Expand Down Expand Up @@ -1068,8 +1073,77 @@ int scandir(const char *path, struct dirent ***res,
*res = names;
return cnt;
}
#else /* !CONFIG_LIBVFSCORE_NONLARGEFILE */
int scandir(const char *path, struct dirent ***res,
int (*sel)(const struct dirent *),
int (*cmp)(const struct dirent **, const struct dirent **))
__attribute__((alias("scandir64")));
#endif /* !CONFIG_LIBVFSCORE_NONLARGEFILE */

#ifdef scandir64
#undef scandir64
#endif
int scandir64(const char *path, struct dirent64 ***res,
int (*sel)(const struct dirent64 *),
int (*cmp)(const struct dirent64 **, const struct dirent64 **))
{
struct dirent64 *de, **names = 0, **tmp;
size_t cnt = 0, len = 0;
int old_errno = errno;
DIR *d;

d = opendir(path);
if (!d)
return -1;

de = readdir64(d);
while (de != NULL) {
if (sel && !sel(de))
continue;

if (cnt >= len) {
len = 2 * len + 1;
if (len > SIZE_MAX / sizeof(*names))
break;

tmp = realloc(names, len * sizeof(*names));
if (!tmp)
break;
names = tmp;
}

names[cnt] = malloc(de->d_reclen);
if (unlikely(!names[cnt]))
break;

memcpy(names[cnt++], de, de->d_reclen);

de = readdir64(d);
}

closedir(d);

if (unlikely(errno)) {
if (names) {
while (cnt-- > 0)
free(names[cnt]);
free(names);
}

return -1;
}
errno = old_errno;

if (cmp)
qsort(names, cnt, sizeof(*names),
(int (*)(const void *, const void *))cmp);

*res = names;
return cnt;
}

UK_TRACEPOINT(trace_vfs_getdents, "%d %#x %hu", int, struct dirent*, size_t);
#if CONFIG_LIBVFSCORE_NONLARGEFILE
UK_TRACEPOINT(trace_vfs_getdents, "%d %p %hu", int, struct dirent*, size_t);
UK_TRACEPOINT(trace_vfs_getdents_ret, "");
UK_TRACEPOINT(trace_vfs_getdents_err, "%d", int);

Expand Down Expand Up @@ -1107,6 +1181,7 @@ UK_SYSCALL_R_DEFINE(int, getdents, int, fd, struct dirent*, dirp,

return (i * sizeof(struct dirent));
}
#endif /* CONFIG_LIBVFSCORE_NONLARGEFILE */

UK_TRACEPOINT(trace_vfs_getdents64, "%d %p %hu", int, struct dirent64 *, size_t);
UK_TRACEPOINT(trace_vfs_getdents64_ret, "");
Expand Down Expand Up @@ -1146,6 +1221,17 @@ UK_SYSCALL_R_DEFINE(int, getdents64, int, fd, struct dirent64 *, dirp,
return (i * sizeof(struct dirent64));
}

/**
* If we use an old libc version, that does not use the largefile syscalls as
* the default ones, we want to use proper Linux `dirent` and `dirent64`
* structures, in order to maintain compatibility.
* Since the filesystem `READDIR` vop uses `dirent64`, we will convert to
* a `dirent` structure within the `readdir_r` call.
*
* When this is not the case, we can simply use `#define dirent dirent64` and
* alias the `readdir*` functions to their `*64` equivalent.
*/
#if CONFIG_LIBVFSCORE_NONLARGEFILE
struct dirent *readdir(DIR *dir)
{
static __thread struct dirent entry;
Expand All @@ -1158,39 +1244,95 @@ struct dirent *readdir(DIR *dir)

int readdir_r(DIR *dir, struct dirent *entry, struct dirent **result)
{
int error;
/**
* `sys_readdir()` will end up calling `VOP_READDIR`, which expects
* a dirent64 structure instead of the `struct dirent` entry parameter.
*/
struct dirent64 entry64;
struct vfscore_file *fp;
int error;

*result = NULL;

trace_vfs_readdir(dir->fd, entry);
error = fget(dir->fd, &fp);
if (!error) {
error = sys_readdir(fp, entry);
fdrop(fp);
if (error) {
trace_vfs_readdir_err(error);
} else {
trace_vfs_readdir_ret();
}
}
// Our dirent has (like Linux) a d_reclen field, but a constant size.
entry->d_reclen = sizeof(*entry);

if (error) {
*result = NULL;
} else {
*result = entry;
trace_vfs_readdir(dir->fd, entry);
error = fget(dir->fd, &fp);
if (unlikely(error))
return error;

error = sys_readdir(fp, &entry64);
fdrop(fp);
if (unlikely(error)) {
trace_vfs_readdir_err(error);

return error == ENOENT ? 0 : error;
}
return error == ENOENT ? 0 : error;

trace_vfs_readdir_ret();

entry->d_ino = entry64.d_ino;
entry->d_type = entry64.d_type;
entry->d_off = entry64.d_off;
memcpy(entry->d_name, entry64.d_name, sizeof(entry64.d_name));

*result = entry;

return 0;
}
#else /* !CONFIG_LIBVFSCORE_NONLARGEFILE */
struct dirent *readdir(DIR *dir) __attribute__((alias("readdir64")));
int readdir_r(DIR *dir, struct dirent *entry, struct dirent **result)
__attribute__((alias("readdir64_r")));
#endif /* !CONFIG_LIBVFSCORE_NONLARGEFILE */

// FIXME: in 64bit dirent64 and dirent are identical, so it's safe to alias
#ifdef readdir64_r
#undef readdir64_r
#endif
int readdir64_r(DIR *dir, struct dirent64 *entry,
struct dirent64 **result)
__attribute__((alias("readdir_r")));
{
struct vfscore_file *fp;
int error;

*result = NULL;

// Our dirent has (like Linux) a d_reclen field, but a constant size.
entry->d_reclen = sizeof(*entry);

trace_vfs_readdir64(dir->fd, entry);
error = fget(dir->fd, &fp);
if (unlikely(error))
return error;

error = sys_readdir(fp, entry);
fdrop(fp);
if (unlikely(error)) {
trace_vfs_readdir_err64(error);

return error == ENOENT ? 0 : error;
}

trace_vfs_readdir_ret64();

*result = entry;

return 0;
}

#ifdef readdir64
#undef readdir64
struct dirent *readdir64(DIR *dir) __attribute__((alias("readdir")));
#endif
struct dirent64 *readdir64(DIR *dir)
{
static __thread struct dirent64 entry;
struct dirent64 *result;

errno = readdir64_r(dir, &entry, &result);

return result;
}

void rewinddir(DIR *dirp)
{
Expand Down
4 changes: 2 additions & 2 deletions lib/vfscore/syscalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ check_dir_empty(char *path)
{
int error;
struct vfscore_file *fp;
struct dirent dir;
struct dirent64 dir;

DPRINTF(VFSDB_SYSCALL, ("check_dir_empty\n"));

Expand All @@ -495,7 +495,7 @@ check_dir_empty(char *path)
}

int
sys_readdir(struct vfscore_file *fp, struct dirent *dir)
sys_readdir(struct vfscore_file *fp, struct dirent64 *dir)
{
struct vnode *dvp;
int error;
Expand Down

0 comments on commit 2ea08f3

Please sign in to comment.