From f08bf57e9895b5a984bf1c125c90a48d2390d15a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 7 Nov 2025 17:31:28 -0800 Subject: [PATCH 1/3] Reimplement directory streams The main goal of this commit is to simplify what's located in the descriptor table of libc. Primarily directory streams are no longer located in the table and are instead stored directly in the `DIR*` structure. Additionally the wasip1-style `cookie` is removed in favor of a wasip2-specific offset counter. Along the way `__wasilibc_nocwd_scandirat` was rewritten for both wasip1 and wasip2 to be built on `openat` + `fdopendir`. This removes WASI-version-specific code and makes this look more similar to `scandir` elsewhere in libc. --- .../cloudlibc/src/libc/dirent/dirent_impl.h | 10 + .../cloudlibc/src/libc/dirent/fdclosedir.c | 5 + .../cloudlibc/src/libc/dirent/fdopendir.c | 34 +- .../cloudlibc/src/libc/dirent/readdir.c | 155 +++++---- .../cloudlibc/src/libc/dirent/rewinddir.c | 6 + .../cloudlibc/src/libc/dirent/scandirat.c | 303 +++--------------- .../cloudlibc/src/libc/dirent/seekdir.c | 6 + .../cloudlibc/src/libc/dirent/telldir.c | 4 + .../headers/private/wasi/descriptor_table.h | 21 -- .../headers/private/wasi/file_utils.h | 44 --- .../sources/__wasilibc_fd_renumber.c | 3 - libc-bottom-half/sources/__wasilibc_rmdirat.c | 4 +- 12 files changed, 170 insertions(+), 425 deletions(-) diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/dirent_impl.h b/libc-bottom-half/cloudlibc/src/libc/dirent/dirent_impl.h index 642400a05..764615f25 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/dirent_impl.h +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/dirent_impl.h @@ -8,6 +8,10 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#endif + struct dirent; #define DIRENT_DEFAULT_BUFFER_SIZE 4096 @@ -15,6 +19,11 @@ struct dirent; struct _DIR { // Directory file descriptor and cookie. int fd; +#ifdef __wasilibc_use_wasip2 + filesystem_own_directory_entry_stream_t stream; + size_t skip; + size_t offset; +#else __wasi_dircookie_t cookie; // Read buffer. @@ -22,6 +31,7 @@ struct _DIR { size_t buffer_processed; size_t buffer_size; size_t buffer_used; +#endif // Object returned by readdir(). struct dirent *dirent; diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/fdclosedir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/fdclosedir.c index cabe70232..912c84c2c 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/fdclosedir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/fdclosedir.c @@ -9,7 +9,12 @@ int fdclosedir(DIR *dirp) { int fd = dirp->fd; +#ifdef __wasilibc_use_wasip2 + if (dirp->stream.__handle != 0) + filesystem_directory_entry_stream_drop_own(dirp->stream); +#else free(dirp->buffer); +#endif free(dirp->dirent); free(dirp); return fd; diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c index 0d28888c0..34e983737 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c @@ -20,16 +20,12 @@ DIR *fdopendir(int fd) { DIR *dirp = malloc(sizeof(*dirp)); if (dirp == NULL) return NULL; - dirp->buffer = malloc(DIRENT_DEFAULT_BUFFER_SIZE); - if (dirp->buffer == NULL) { - free(dirp); - return NULL; - } - #ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle filesystem_borrow_descriptor_t file_handle; if (!fd_to_file_handle(fd, &file_handle)) { + free(dirp); errno = EBADF; return NULL; } @@ -41,34 +37,26 @@ DIR *fdopendir(int fd) { &result, &error_code); if (!ok) { - free(dirp->buffer); free(dirp); translate_error(error_code); return NULL; } dirp->fd = fd; - // Add an internal handle for the buffer - descriptor_table_entry_t new_entry; - new_entry.tag = DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM; - directory_stream_entry_t stream_info; - stream_info.directory_stream = result; - stream_info.directory_file_handle = file_handle; - stream_info.directory_state = DIRECTORY_STATE_FILE; - new_entry.directory_stream_info = stream_info; - int new_fd = -1; - if (!descriptor_table_update(dirp->fd, new_entry)) { - errno = EBADF; - return NULL; - } - dirp->cookie = __WASI_DIRCOOKIE_START; - dirp->buffer_processed = 0; - dirp->buffer_size = DIRENT_DEFAULT_BUFFER_SIZE; + dirp->stream = result; + dirp->skip = 0; + dirp->offset = 0; dirp->dirent = NULL; dirp->dirent_size = 1; return dirp; #else + dirp->buffer = malloc(DIRENT_DEFAULT_BUFFER_SIZE); + if (dirp->buffer == NULL) { + free(dirp); + return NULL; + } + // Ensure that this is really a directory by already loading the first // chunk of data. __wasi_errno_t error = diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c index 518a92a16..b1a9d0701 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c @@ -39,26 +39,79 @@ static_assert(DT_UNKNOWN == __WASI_FILETYPE_UNKNOWN, "Value mismatch"); while (new_size < (target_size)) \ new_size *= 2; \ void *new_buffer = realloc(buffer, new_size); \ - if (new_buffer == NULL) \ + if (new_buffer == NULL) { \ + errno = ENOMEM; \ return NULL; \ + } \ (buffer) = new_buffer; \ (buffer_size) = new_size; \ } \ } while (0) #ifdef __wasilibc_use_wasip2 -struct dirent *readdir(DIR *dirp) { - // Translate the file descriptor to an internal handle - filesystem_borrow_directory_entry_stream_t stream; - filesystem_borrow_descriptor_t parent_handle; - read_directory_state_t state; - if (!fd_to_directory_stream(dirp->fd, &stream, &parent_handle, &state)) { + +static int ensure_has_directory_stream(DIR *dirp, filesystem_borrow_descriptor_t *handle) { + if (!fd_to_file_handle(dirp->fd, handle)) { errno = EBADF; + return -1; + } + + if (dirp->stream.__handle != 0) + return 0; + + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_read_directory(*handle, + &dirp->stream, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + return 0; +} + +struct dirent *readdir(DIR *dirp) { + filesystem_metadata_hash_value_t metadata; + filesystem_error_code_t error_code; + filesystem_borrow_descriptor_t dir_handle; + + if (ensure_has_directory_stream(dirp, &dir_handle) < 0) return NULL; + + // Yield '.' first if the offset is 0. Note that `d_ino` is from the metadata + // hash of the directory itself. + if (dirp->offset == 0) { + dirp->offset += 1; + GROW(dirp->dirent, dirp->dirent_size, offsetof(struct dirent, d_name) + 2); + bool ok = filesystem_method_descriptor_metadata_hash(dir_handle, + &metadata, + &error_code); + if (!ok) { + translate_error(error_code); + return NULL; + } + dirp->dirent->d_ino = metadata.lower; + dirp->dirent->d_type = DT_DIR; + dirp->dirent->d_name[0] = '.'; + dirp->dirent->d_name[1] = 0; + return dirp->dirent; + } + + // Yield '..' next if the offset is 1. Note that `d_ino` is set to 0 to + // avoid opening the parent directory here. + if (dirp->offset == 1) { + dirp->offset += 1; + GROW(dirp->dirent, dirp->dirent_size, offsetof(struct dirent, d_name) + 3); + dirp->dirent->d_ino = 0; + dirp->dirent->d_type = DT_DIR; + dirp->dirent->d_name[0] = '.'; + dirp->dirent->d_name[1] = '.'; + dirp->dirent->d_name[2] = 0; + return dirp->dirent; } + filesystem_borrow_directory_entry_stream_t stream = filesystem_borrow_directory_entry_stream(dirp->stream); filesystem_option_directory_entry_t dir_entry_optional; - filesystem_error_code_t error_code; bool ok = filesystem_method_directory_entry_stream_read_directory_entry(stream, &dir_entry_optional, &error_code); @@ -67,71 +120,37 @@ struct dirent *readdir(DIR *dirp) { return NULL; } - bool return_dot = false; - bool return_dot_dot = false; - if (!dir_entry_optional.is_some) { - // End-of-file; check if we should return '.' or '..' - if (state == DIRECTORY_STATE_FILE) { - return_dot = true; - directory_stream_enter_state(dirp->fd, DIRECTORY_STATE_RETURNED_DOT); - } - else if (state == DIRECTORY_STATE_RETURNED_DOT) { - return_dot_dot = true; - directory_stream_enter_state(dirp->fd, DIRECTORY_STATE_RETURNED_DOT_DOT); - } - else { - remove_and_drop_directory_stream(dirp->fd); - return NULL; - } - } + // Reached end-of-directory? Return null. + if (!dir_entry_optional.is_some) + return NULL; filesystem_directory_entry_t dir_entry = dir_entry_optional.val; - struct dirent *dirent; - if (!(return_dot || return_dot_dot)) { - // Ensure that the dirent is large enough to fit the filename - size_t the_size = offsetof(struct dirent, d_name); - GROW(dirp->dirent, dirp->dirent_size, - the_size + dir_entry.name.len + 1); - dirent = dirp->dirent; - } else { - dirent = malloc(sizeof(dirent)); - if (dirent == NULL) { - errno = ENOMEM; - return NULL; - } - size_t the_size = offsetof(struct dirent, d_name); - int name_len = return_dot ? 1 : 2; - int32_t dirent_size = sizeof(dirent); - GROW(dirent, dirent_size, the_size + name_len + 1); - strcpy(dirent->d_name, return_dot ? "." : ".."); - dirent->d_type = DT_DIR; - } - - // Get the inode number - if (return_dot || return_dot_dot) - dirent->d_ino = -1; - else { - filesystem_path_flags_t path_flags = 0; // Don't follow symlinks - filesystem_metadata_hash_value_t metadata; - wasip2_string_t name_to_use; - ok = filesystem_method_descriptor_metadata_hash_at(parent_handle, - path_flags, - &dir_entry.name, - &metadata, - &error_code); - if (!ok) { - translate_error(error_code); - remove_and_drop_directory_stream(dirp->fd); - return NULL; - } - dirent->d_ino = metadata.lower; - dirent->d_type = dir_entry_type_to_d_type(dir_entry.type); - memcpy(dirent->d_name, dir_entry.name.ptr, dir_entry.name.len); - dirent->d_name[dir_entry.name.len] = '\0'; + // Ensure that the dirent is large enough to fit the filename + size_t the_size = offsetof(struct dirent, d_name); + GROW(dirp->dirent, dirp->dirent_size, the_size + dir_entry.name.len + 1); + + // Fill out `d_type` and `d_name` + dirp->dirent->d_type = dir_entry_type_to_d_type(dir_entry.type); + memcpy(dirp->dirent->d_name, dir_entry.name.ptr, dir_entry.name.len); + dirp->dirent->d_name[dir_entry.name.len] = '\0'; + + // Fill out `d_ino` with the metadata hash. + filesystem_path_flags_t path_flags = 0; // Don't follow symlinks + ok = filesystem_method_descriptor_metadata_hash_at(dir_handle, + path_flags, + &dir_entry.name, + &metadata, + &error_code); + wasip2_string_free(&dir_entry.name); + if (!ok) { + translate_error(error_code); + return NULL; } + dirp->dirent->d_ino = metadata.lower; + dirp->offset += 1; - return dirent; + return dirp->dirent; } #else struct dirent *readdir(DIR *dirp) { diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/rewinddir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/rewinddir.c index 8dd58827a..a3fa3d442 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/rewinddir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/rewinddir.c @@ -8,8 +8,14 @@ #include "dirent_impl.h" void rewinddir(DIR *dirp) { +#if __wasilibc_use_wasip2 + dirp->stream.__handle = 0; + dirp->skip = 0; + dirp->offset = 0; +#else // Update cookie. dirp->cookie = __WASI_DIRCOOKIE_START; // Mark entire buffer as processed to force a read of new data. dirp->buffer_used = dirp->buffer_processed = dirp->buffer_size; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c b/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c index 38dfbb673..8301c30d6 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c @@ -25,271 +25,48 @@ static int sel_true(const struct dirent *de) { return 1; } -int __wasilibc_nocwd_scandirat(int dirfd, const char *dir, struct dirent ***namelist, +int __wasilibc_nocwd_scandirat(int dirfd, const char *dir, struct dirent ***res, 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; - -#ifdef __wasilibc_use_wasip2 - // Open the directory. - int fd = __wasilibc_nocwd_openat_nomode(dirfd, dir, O_RDONLY | O_NONBLOCK | O_DIRECTORY); - if (fd == -1) - return -1; - DIR *dirp = fdopendir(fd); - if (!dirp) { - close(fd); - return -1; - } - fd = dirp->fd; - - // Translate the file descriptor to an internal handle - filesystem_borrow_directory_entry_stream_t stream; - filesystem_borrow_descriptor_t parent_file_handle; - read_directory_state_t state; - if (!fd_to_directory_stream(fd, &stream, &parent_file_handle, &state)) { - errno = EBADF; - close(fd); - return -1; - } - - // Space for the array to return to the caller. - struct dirent **dirents = NULL; - size_t dirents_size = 0; - size_t dirents_used = 0; - - bool ok = true; - filesystem_option_directory_entry_t dir_entry_optional; - filesystem_error_code_t error_code; - bool handle_dot = false; - bool handle_dot_dot = false; - while (true) { - ok = filesystem_method_directory_entry_stream_read_directory_entry(stream, - &dir_entry_optional, - &error_code); - if (!ok) { - translate_error(error_code); - return -1; - } - if (!dir_entry_optional.is_some) { - // All directory entries have been read; handle . and .. - if (!handle_dot && !handle_dot_dot) { - handle_dot = true; - } else if (handle_dot && !handle_dot_dot) { - handle_dot_dot = true; - handle_dot = false; - } else - break; - } - - // Create the new directory entry - size_t name_len = handle_dot ? 1 : handle_dot_dot ? 2 : dir_entry_optional.val.name.len; - struct dirent *dirent = - malloc(offsetof(struct dirent, d_name) + name_len + 1); - if (dirent == NULL) { - errno = EINVAL; - remove_and_drop_directory_stream(fd); - close(fd); - return -1; - } - dirent->d_type = (handle_dot || handle_dot_dot) ? DT_DIR : dir_entry_type_to_d_type(dir_entry_optional.val.type); - if (handle_dot) - memcpy(dirent->d_name, ".", name_len); - else if (handle_dot_dot) - memcpy(dirent->d_name, "..", name_len); - else - memcpy(dirent->d_name, dir_entry_optional.val.name.ptr, name_len); - dirent->d_name[name_len] = '\0'; - - if (!(handle_dot || handle_dot_dot)) { - // Do an `fstatat` to get the inode number. - if (fstatat(fd, dirent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0) { - errno = EBADF; - remove_and_drop_directory_stream(fd); - close(fd); - return -1; - } - // Fill in the inode. - dirent->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. - dirent->d_type = __wasilibc_iftodt(statbuf.st_mode & S_IFMT); - } else { - dirent->d_ino = -1; - } - - if (sel(dirent)) { - // Add the entry to the results - if (dirents_used == dirents_size) { - dirents_size = dirents_size < 8 ? 8 : dirents_size * 2; - struct dirent **new_dirents = - realloc(dirents, dirents_size * sizeof(*dirents)); - if (new_dirents == NULL) { - free(dirent); - free(dirents); - remove_and_drop_directory_stream(fd); - close(fd); - errno = EBADF; + int (*cmp)(const struct dirent **, const struct dirent **)) { + dirfd = openat(dirfd, dir, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) { return -1; - } - dirents = new_dirents; } - dirents[dirents_used++] = dirent; - } else { - // Discard the entry. - free(dirent); - } - } - - // Sort results and return them - remove_and_drop_directory_stream(fd); - (qsort)(dirents, dirents_used, sizeof(*dirents), - (int (*)(const void *, const void *))compar); - *namelist = dirents; - close(fd); - return dirents_used; -#else - // Open the directory. - int fd = __wasilibc_nocwd_openat_nomode(dirfd, dir, O_RDONLY | O_NONBLOCK | O_DIRECTORY); - if (fd == -1) - return -1; - - // Allocate a read buffer for the directory entries. - size_t buffer_size = DIRENT_DEFAULT_BUFFER_SIZE; - char *buffer = malloc(buffer_size); - if (buffer == NULL) { - close(fd); - return -1; - } - size_t buffer_processed = buffer_size; - size_t buffer_used = buffer_size; - - // Space for the array to return to the caller. - struct dirent **dirents = NULL; - size_t dirents_size = 0; - size_t dirents_used = 0; - - __wasi_dircookie_t cookie = __WASI_DIRCOOKIE_START; - for (;;) { - // Extract the next dirent header. - size_t buffer_left = buffer_used - buffer_processed; - if (buffer_left < sizeof(__wasi_dirent_t)) { - // End-of-file. - if (buffer_used < buffer_size) - break; - goto read_entries; - } - __wasi_dirent_t entry; - memcpy(&entry, buffer + buffer_processed, sizeof(entry)); - - size_t entry_size = sizeof(__wasi_dirent_t) + entry.d_namlen; - if (entry.d_namlen == 0) { - // Invalid pathname length. Skip the entry. - buffer_processed += entry_size; - continue; - } - - // The entire entry must be present in buffer space. If not, read - // the entry another time. Ensure that the read buffer is large - // enough to fit at least this single entry. - if (buffer_left < entry_size) { - while (buffer_size < entry_size) - buffer_size *= 2; - char *new_buffer = realloc(buffer, buffer_size); - if (new_buffer == NULL) - goto bad; - buffer = new_buffer; - goto read_entries; - } - - // Skip entries having null bytes in the filename. - const char *name = buffer + buffer_processed + sizeof(entry); - buffer_processed += entry_size; - if (memchr(name, '\0', entry.d_namlen) != NULL) - continue; - - // Create the new directory entry. - struct dirent *dirent = - malloc(offsetof(struct dirent, d_name) + entry.d_namlen + 1); - if (dirent == NULL) - goto bad; - 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)) { - // Add the entry to the results. - if (dirents_used == dirents_size) { - dirents_size = dirents_size < 8 ? 8 : dirents_size * 2; - struct dirent **new_dirents = - realloc(dirents, dirents_size * sizeof(*dirents)); - if (new_dirents == NULL) { - free(dirent); - goto bad; - } - dirents = new_dirents; - } - dirents[dirents_used++] = dirent; - } else { - // Discard the entry. - free(dirent); - } - continue; - - read_entries:; - // Load more directory entries and continue. - // TODO: Remove the cast on `buffer` once the witx is updated with char8 support. - __wasi_errno_t error = __wasi_fd_readdir(fd, (uint8_t *)buffer, buffer_size, - cookie, &buffer_used); - if (error != 0) { - errno = error; - goto bad; - } - buffer_processed = 0; - } - - // Sort results and return them. - free(buffer); - close(fd); - (qsort)(dirents, dirents_used, sizeof(*dirents), - (int (*)(const void *, const void *))compar); - *namelist = dirents; - return dirents_used; - -bad: - // Deallocate partially created results. - for (size_t i = 0; i < dirents_used; ++i) - free(dirents[i]); - free(dirents); - free(buffer); - close(fd); - return -1; -#endif + // Note that below this is a copy of `scandir.c` located in the top-half + // of this library. + DIR *d = fdopendir(dirfd); + struct dirent *de, **names=0, **tmp; + size_t cnt=0, len=0; + int old_errno = errno; + + if (!d) return -1; + + while ((errno=0), (de = readdir(d))) { + 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; + } + size_t size = sizeof(struct dirent) + strlen(de->d_name) + 1; + names[cnt] = malloc(size); + if (!names[cnt]) break; + memcpy(names[cnt++], de, size); + } + + closedir(d); + + if (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; } diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/seekdir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/seekdir.c index a865d92c7..72625cc65 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/seekdir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/seekdir.c @@ -7,9 +7,15 @@ #include "dirent_impl.h" void seekdir(DIR *dirp, long loc) { +#if __wasilibc_use_wasip2 + dirp->stream.__handle = 0; + dirp->skip = loc; + dirp->offset = 0; +#else // Update cookie. dirp->cookie = (unsigned long)loc; // Mark entire buffer as processed to force a read of new data. // TODO(ed): We could prevent a read if the offset is in the buffer. dirp->buffer_used = dirp->buffer_processed = dirp->buffer_size; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c index 05687eef8..ec77d529a 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c @@ -7,5 +7,9 @@ #include "dirent_impl.h" long telldir(DIR *dirp) { +#if __wasilibc_use_wasip2 + return dirp->offset; +#else return dirp->cookie; +#endif } diff --git a/libc-bottom-half/headers/private/wasi/descriptor_table.h b/libc-bottom-half/headers/private/wasi/descriptor_table.h index 98218d397..5b7a0bbb4 100644 --- a/libc-bottom-half/headers/private/wasi/descriptor_table.h +++ b/libc-bottom-half/headers/private/wasi/descriptor_table.h @@ -108,25 +108,6 @@ typedef struct { udp_socket_state_t state; } udp_socket_t; -typedef enum read_directory_state_t { - DIRECTORY_STATE_FILE, - DIRECTORY_STATE_RETURNED_DOT, - DIRECTORY_STATE_RETURNED_DOT_DOT -} read_directory_state_t; - -typedef struct { -// Stream for contents of directory - filesystem_own_directory_entry_stream_t directory_stream; -// File handle for the directory being listed -// (so that metadata hashes can be accessed). - filesystem_borrow_descriptor_t directory_file_handle; -// State that reflects whether . and .. have been handled yet; -// this is used by readdir() since each call handles a single -// directory entry, and this state needs to be maintained across -// calls - read_directory_state_t directory_state; -} directory_stream_entry_t; - // Stream representing an open file. typedef struct { // File was opened for reading @@ -155,14 +136,12 @@ typedef struct { DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET, DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET, DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE, - DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM, DESCRIPTOR_TABLE_ENTRY_FILE_STREAM } tag; union { tcp_socket_t tcp_socket; udp_socket_t udp_socket; file_t file; - directory_stream_entry_t directory_stream_info; file_stream_t stream; }; } descriptor_table_entry_t; diff --git a/libc-bottom-half/headers/private/wasi/file_utils.h b/libc-bottom-half/headers/private/wasi/file_utils.h index 06857b934..eff29cc40 100644 --- a/libc-bottom-half/headers/private/wasi/file_utils.h +++ b/libc-bottom-half/headers/private/wasi/file_utils.h @@ -37,55 +37,11 @@ static bool fd_to_file_handle_allow_open(int fd, filesystem_borrow_descriptor_t* *result = entry->stream.file_info.file_handle; else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) *result = entry->file.file_handle; - else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) - *result = entry->directory_stream_info.directory_file_handle; else return false; return true; } -// Succeed only if fd is bound to a directory stream in the descriptor table -static bool fd_to_directory_stream(int fd, filesystem_borrow_directory_entry_stream_t* result_stream, - filesystem_borrow_descriptor_t* result_fd, - read_directory_state_t* result_state) { - descriptor_table_entry_t *entry = 0; - if (!descriptor_table_get_ref(fd, &entry)) - return false; - if (entry->tag != DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) { - return false; - } - *result_stream = filesystem_borrow_directory_entry_stream(entry->directory_stream_info.directory_stream); - *result_fd = entry->directory_stream_info.directory_file_handle; - *result_state = entry->directory_stream_info.directory_state; - return true; -} - -// Does nothing if fd is not in the descriptor table or is not bound to a directory stream -static void directory_stream_enter_state(int fd, read_directory_state_t state) { - descriptor_table_entry_t *entry = 0; - if (!descriptor_table_get_ref(fd, &entry)) - return; - - if (entry->tag == DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) - entry->directory_stream_info.directory_state = state; -} - -// Does nothing if fd is not in the descriptor table or is not bound to a directory stream -static void remove_and_drop_directory_stream(int fd) { - descriptor_table_entry_t *entry = 0; - if (!descriptor_table_get_ref(fd, &entry)) - return; - - if (entry->tag == DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) { - filesystem_directory_entry_stream_drop_own(entry->directory_stream_info.directory_stream); - filesystem_borrow_descriptor_t file_handle = entry->directory_stream_info.directory_file_handle; - entry->tag = DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE; - entry->file.file_handle = file_handle; - entry->file.readable = true; - entry->file.writable = false; - } -} - // The following three functions are used for lazily initializing stdin/stdout/stderr in // the descriptor table. These file descriptors never need to be removed from the // descriptor table. diff --git a/libc-bottom-half/sources/__wasilibc_fd_renumber.c b/libc-bottom-half/sources/__wasilibc_fd_renumber.c index 1b5592f71..2953bc8c5 100644 --- a/libc-bottom-half/sources/__wasilibc_fd_renumber.c +++ b/libc-bottom-half/sources/__wasilibc_fd_renumber.c @@ -118,9 +118,6 @@ int close(int fd) { case DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE: drop_file_handle(entry.file.file_handle); break; - case DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM: - drop_directory_stream(entry.directory_stream_info.directory_stream); - break; case DESCRIPTOR_TABLE_ENTRY_FILE_STREAM: if (entry.stream.read_pollable.__handle != 0) poll_pollable_drop_own(entry.stream.read_pollable); diff --git a/libc-bottom-half/sources/__wasilibc_rmdirat.c b/libc-bottom-half/sources/__wasilibc_rmdirat.c index ae661d4c5..465b30860 100644 --- a/libc-bottom-half/sources/__wasilibc_rmdirat.c +++ b/libc-bottom-half/sources/__wasilibc_rmdirat.c @@ -19,9 +19,7 @@ int __wasilibc_nocwd___wasilibc_rmdirat(int fd, const char *path) { errno = EBADF; return EBADF; } - if (entry->tag == DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) - file_handle = entry->directory_stream_info.directory_file_handle; - else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) file_handle = entry->file.file_handle; else { errno = EINVAL; From 29aea3247be6f4965759a4b37ca4bd117bd9b274 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 7 Nov 2025 17:36:52 -0800 Subject: [PATCH 2/3] Handle `seekdir` as well --- libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c index b1a9d0701..a546d92d4 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c @@ -70,7 +70,7 @@ static int ensure_has_directory_stream(DIR *dirp, filesystem_borrow_descriptor_t return 0; } -struct dirent *readdir(DIR *dirp) { +static struct dirent *readdir_next(DIR *dirp) { filesystem_metadata_hash_value_t metadata; filesystem_error_code_t error_code; filesystem_borrow_descriptor_t dir_handle; @@ -152,6 +152,16 @@ struct dirent *readdir(DIR *dirp) { return dirp->dirent; } + +struct dirent *readdir(DIR *dirp) { + struct dirent *result = readdir_next(dirp); + while (result != NULL && dirp->skip > 0) { + dirp->skip -= 1; + result = readdir_next(dirp); + } + return result; +} + #else struct dirent *readdir(DIR *dirp) { for (;;) { From d039b0180652f7a25e29e7b1c1e9155982583eff Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Nov 2025 11:44:25 -0800 Subject: [PATCH 3/3] Add simple rewinddir/seekdir test --- .../cloudlibc/src/libc/dirent/telldir.c | 2 +- test/CMakeLists.txt | 2 + test/src/rewinddir.c | 55 ++++++++++++++ test/src/seekdir.c | 71 +++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 test/src/rewinddir.c create mode 100644 test/src/seekdir.c diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c index ec77d529a..008448566 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/telldir.c @@ -8,7 +8,7 @@ long telldir(DIR *dirp) { #if __wasilibc_use_wasip2 - return dirp->offset; + return dirp->offset + dirp->skip; #else return dirp->cookie; #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a45eb5549..ce666914c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -304,6 +304,8 @@ add_wasilibc_test(time_and_times.c LDFLAGS -lwasi-emulated-process-clocks) add_wasilibc_test(time.c) add_wasilibc_test(utime.c FS) +add_wasilibc_test(rewinddir.c FS) +add_wasilibc_test(seekdir.c FS) if (TARGET_TRIPLE MATCHES "-threads") add_wasilibc_test(busywait.c) diff --git a/test/src/rewinddir.c b/test/src/rewinddir.c new file mode 100644 index 000000000..d83bb5733 --- /dev/null +++ b/test/src/rewinddir.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +static int countdir(DIR *dir) +{ + int count = 0; + while (readdir(dir) != NULL) { + int is_error = errno != 0; + TEST(!is_error); + count++; + } + return count; +} + +int main(void) +{ + int fd; + int flags = 0; + + // Create the directory + TEST(mkdir("test", 0755)==0); + TEST((fd = open("test/a", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + TEST((fd = open("test/b", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + TEST((fd = open("test/c", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + + DIR* dir = opendir("test"); + TEST(dir != NULL); + TEST(countdir(dir) == 5); + TEST(countdir(dir) == 0); + rewinddir(dir); + TEST(countdir(dir) == 5); + TEST(countdir(dir) == 0); + + return t_status; +} + diff --git a/test/src/seekdir.c b/test/src/seekdir.c new file mode 100644 index 000000000..9b3a6a2fc --- /dev/null +++ b/test/src/seekdir.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +static int countdir(DIR *dir) +{ + int count = 0; + while (readdir(dir) != NULL) { + int is_error = errno != 0; + TEST(!is_error); + count++; + } + return count; +} + +int main(void) +{ + int fd; + int flags = 0; + + // Create the directory + TEST(mkdir("test", 0755)==0); + TEST((fd = open("test/a", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + TEST((fd = open("test/b", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + TEST((fd = open("test/c", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + + int positions[5]; + int count = 0; + + DIR* dir = opendir("test"); + TEST(dir != NULL); + struct dirent *entry; + + int is_end; + while(1) { + positions[count] = telldir(dir); + if (readdir(dir) == NULL) + break; + count++; + } + TEST(count == 5); + + for (int i = 0; i < count; i++) { + seekdir(dir, positions[i]); + TEST(telldir(dir) == positions[i]); + int c = countdir(dir); + TEST(c == (5 - i)); + } + + return t_status; +} + +