Skip to content

Commit

Permalink
If fd_readdir returns a zero inode, call fstatat to get the inode…
Browse files Browse the repository at this point in the history
… value. (WebAssembly#345)

* If `fd_readdir` returns a zero inode, call `fstatat` to get the inode value.

On some systems, `fd_readdir` may not implement the `d_ino` field and
may set it to zero. When this happens, have wasi-libc call `fstatat` to
get the inode number.

See the discussion in
WebAssembly/wasi-filesystem#65 for details.

* Update the `d_type` field too, in case it changes.
  • Loading branch information
Dan Gohman authored and john-sharratt committed Mar 5, 2023
1 parent 812e928 commit 5ec38e7
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
30 changes: 29 additions & 1 deletion libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// SPDX-License-Identifier: BSD-2-Clause

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#include <assert.h>
#include <wasi/api.h>
Expand Down Expand Up @@ -77,10 +80,35 @@ struct dirent *readdir(DIR *dirp) {
GROW(dirp->dirent, dirp->dirent_size,
offsetof(struct dirent, d_name) + entry.d_namlen + 1);
struct dirent *dirent = dirp->dirent;
dirent->d_ino = entry.d_ino;
dirent->d_type = entry.d_type;
memcpy(dirent->d_name, name, entry.d_namlen);
dirent->d_name[entry.d_namlen] = '\0';

// `fd_readdir` implementations may set the inode field to zero if the
// the inode number is unknown. In that case, do an `fstatat` to get the
// inode number.
off_t d_ino = entry.d_ino;
unsigned char d_type = entry.d_type;
if (d_ino == 0) {
struct stat statbuf;
if (fstatat(dirp->fd, dirent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0) {
if (errno == ENOENT) {
// The file disappeared before we could read it, so skip it.
continue;
}
return NULL;
}

// Fill in the inode.
d_ino = statbuf.st_ino;

// In case someone raced with us and replaced the object with this name
// with another of a different type, update the type too.
d_type = __wasilibc_iftodt(statbuf.st_mode & S_IFMT);
}
dirent->d_ino = d_ino;
dirent->d_type = d_type;

dirp->cookie = entry.d_next;
dirp->buffer_processed += entry_size;
return dirent;
Expand Down
25 changes: 24 additions & 1 deletion libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

#include "dirent_impl.h"

Expand All @@ -21,6 +22,8 @@ static int sel_true(const struct dirent *de) {
int __wasilibc_nocwd_scandirat(int dirfd, const char *dir, struct dirent ***namelist,
int (*sel)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **)) {
struct stat statbuf;

// Match all files if no select function is provided.
if (sel == NULL)
sel = sel_true;
Expand Down Expand Up @@ -89,10 +92,30 @@ int __wasilibc_nocwd_scandirat(int dirfd, const char *dir, struct dirent ***name
malloc(offsetof(struct dirent, d_name) + entry.d_namlen + 1);
if (dirent == NULL)
goto bad;
dirent->d_ino = entry.d_ino;
dirent->d_type = entry.d_type;
memcpy(dirent->d_name, name, entry.d_namlen);
dirent->d_name[entry.d_namlen] = '\0';

// `fd_readdir` implementations may set the inode field to zero if the
// the inode number is unknown. In that case, do an `fstatat` to get the
// inode number.
off_t d_ino = entry.d_ino;
unsigned char d_type = entry.d_type;
if (d_ino == 0) {
if (fstatat(fd, dirent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0) {
return -1;
}

// Fill in the inode.
d_ino = statbuf.st_ino;

// In case someone raced with us and replaced the object with this name
// with another of a different type, update the type too.
d_type = __wasilibc_iftodt(statbuf.st_mode & S_IFMT);
}
dirent->d_ino = d_ino;
dirent->d_type = d_type;

cookie = entry.d_next;

if (sel(dirent)) {
Expand Down

0 comments on commit 5ec38e7

Please sign in to comment.