diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/read.c b/libc-bottom-half/cloudlibc/src/libc/unistd/read.c index 5ce69fede..11e0d15f1 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/read.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/read.c @@ -21,6 +21,13 @@ ssize_t read(int fildes, void *buf, size_t nbyte) { size_t bytes_read; __wasi_errno_t error = __wasi_fd_read(fildes, &iov, 1, &bytes_read); if (error != 0) { + __wasi_fdstat_t fds; + if ((error == __WASI_ERRNO_BADF || error == ENOTCAPABLE) && + __wasi_fd_fdstat_get(fildes, &fds) == 0 && + fds.fs_filetype == __WASI_FILETYPE_DIRECTORY) { + errno = EISDIR; + return -1; + } errno = error == ENOTCAPABLE ? EBADF : error; return -1; } diff --git a/libc-bottom-half/sources/file.c b/libc-bottom-half/sources/file.c index 6e0c22468..8fcb6bf10 100644 --- a/libc-bottom-half/sources/file.c +++ b/libc-bottom-half/sources/file.c @@ -81,6 +81,29 @@ static void file_free(void *data) { free(file); } +// True when `file` refers to a directory. Used to return POSIX EISDIR +// instead of the host's `bad-descriptor`. On wasip2 this runs only after +// `read-via-stream` fails. On wasip3 that call traps, so the check runs +// before opening the stream. +static bool file_is_directory(file_t *file) { + filesystem_error_code_t error; +#ifdef __wasip2__ + filesystem_descriptor_type_t ty; + if (!filesystem_method_descriptor_get_type( + filesystem_borrow_descriptor(file->file_handle), &ty, &error)) + return false; + return ty == FILESYSTEM_DESCRIPTOR_TYPE_DIRECTORY; +#else + filesystem_descriptor_stat_t st; + if (!filesystem_method_descriptor_stat( + filesystem_borrow_descriptor(file->file_handle), &st, &error)) + return false; + bool is_dir = st.type.tag == FILESYSTEM_DESCRIPTOR_TYPE_DIRECTORY; + filesystem_descriptor_stat_free(&st); + return is_dir; +#endif +} + #ifndef __wasip2__ static int file_read_eof(void *data) { file_t *file = (file_t *)data; @@ -94,6 +117,11 @@ static int file_read_eof(void *data) { filesystem_future_result_void_error_code_drop_readable(file->read_result); file->read_result = 0; if (result.is_err) { + if (result.val.err.tag == FILESYSTEM_ERROR_CODE_BAD_DESCRIPTOR && + file_is_directory(file)) { + errno = EISDIR; + return -1; + } translate_error(&result.val.err); return -1; } @@ -115,6 +143,11 @@ static int file_get_read_stream(void *data, wasi_read_t *read) { filesystem_borrow_descriptor(file->file_handle), file->offset, &file->read_stream, &error_code); if (!ok) { + if (error_code == FILESYSTEM_ERROR_CODE_BAD_DESCRIPTOR && + file_is_directory(file)) { + errno = EISDIR; + return -1; + } translate_error(&error_code); return -1; } @@ -123,6 +156,12 @@ static int file_get_read_stream(void *data, wasi_read_t *read) { read->pollable = &file->read_pollable; #else if (!wasip3_io_state_present(&file->read)) { + // wasip3 `read-via-stream` returns a stream, not a result. Wasmtime + // traps with ErrorCode::BadDescriptor on a directory. Check first. + if (file_is_directory(file)) { + errno = EISDIR; + return -1; + } assert(!file->read_result); filesystem_tuple2_stream_u8_future_result_void_error_code_t result; filesystem_method_descriptor_read_via_stream( diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8fe517798..989c704b3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -488,6 +488,7 @@ add_wasilibc_test(ppoll.c FS) add_wasilibc_test(pselect.c FS) add_wasilibc_test(preadvwritev.c FS) add_wasilibc_test(preadwrite.c FS) +add_wasilibc_test(read-dir-eisdir.c FS) add_wasilibc_test(readlink.c FS) add_wasilibc_test(readv.c FS) add_wasilibc_test(rename.c FS) diff --git a/test/src/read-dir-eisdir.c b/test/src/read-dir-eisdir.c new file mode 100644 index 000000000..b91f3e6cc --- /dev/null +++ b/test/src/read-dir-eisdir.c @@ -0,0 +1,42 @@ +#include "test.h" +#include +#include +#include +#include + +#define TEST(c) \ + do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ + } while (0) + +// POSIX read() on a directory fd must fail with EISDIR, not EBADF. +// wasip2 read-via-stream fails with bad-descriptor (mapped to EBADF). +// wasip3 read-via-stream traps with BadDescriptor unless we check type first. +int main(void) { + char buf[16]; + + TEST(mkdir("read-dir-eisdir", 0755) == 0); + int dirfd = open("read-dir-eisdir", O_RDONLY | O_DIRECTORY); + TEST(dirfd > 2); + + TEST(read(dirfd, buf, sizeof buf) == -1 && errno == EISDIR); + + TEST(close(dirfd) == 0); + + // Genuine bad fds stay EBADF; regular files still read. + TEST(read(-1, buf, 1) == -1 && errno == EBADF); + + int fd; + TEST((fd = open("read-dir-eisdir/f", O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "x", 1) == 1); + TEST(lseek(fd, 0, SEEK_SET) == 0); + TEST(read(fd, buf, 1) == 1); + TEST(buf[0] == 'x'); + TEST(close(fd) == 0); + TEST(unlink("read-dir-eisdir/f") == 0); + TEST(rmdir("read-dir-eisdir") == 0); + + return t_status; +}