From e2cad9f7f7987a2fad531fdf3bd7720ce2ab8611 Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues <16357187+eduardomourar@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:14:47 +0100 Subject: [PATCH] fix: empty stat-family paths --- .../wasm32-wasip1-threads/predefined-macros.txt | 1 + expected/wasm32-wasip1/predefined-macros.txt | 1 + expected/wasm32-wasip2/predefined-macros.txt | 1 + expected/wasm32-wasip3-coop/predefined-macros.txt | 1 + expected/wasm32-wasip3/predefined-macros.txt | 1 + .../cloudlibc/src/libc/sys/stat/fstatat.c | 9 +++++++++ libc-bottom-half/headers/public/__header_fcntl.h | 1 + libc-bottom-half/sources/posix.c | 15 +++++++++++++++ test/src/stat.c | 15 +++++++++++++++ 9 files changed, 45 insertions(+) diff --git a/expected/wasm32-wasip1-threads/predefined-macros.txt b/expected/wasm32-wasip1-threads/predefined-macros.txt index c1c3749d7..fe08cf2be 100644 --- a/expected/wasm32-wasip1-threads/predefined-macros.txt +++ b/expected/wasm32-wasip1-threads/predefined-macros.txt @@ -46,6 +46,7 @@ #define ARG_MAX 131072 #define ARMAG "!\n" #define AT_EACCESS (0x0) +#define AT_EMPTY_PATH (0x8) #define AT_FDCWD (-2) #define AT_REMOVEDIR (0x4) #define AT_SYMLINK_FOLLOW (0x2) diff --git a/expected/wasm32-wasip1/predefined-macros.txt b/expected/wasm32-wasip1/predefined-macros.txt index b32032660..19b6dd4e4 100644 --- a/expected/wasm32-wasip1/predefined-macros.txt +++ b/expected/wasm32-wasip1/predefined-macros.txt @@ -46,6 +46,7 @@ #define ARG_MAX 131072 #define ARMAG "!\n" #define AT_EACCESS (0x0) +#define AT_EMPTY_PATH (0x8) #define AT_FDCWD (-2) #define AT_REMOVEDIR (0x4) #define AT_SYMLINK_FOLLOW (0x2) diff --git a/expected/wasm32-wasip2/predefined-macros.txt b/expected/wasm32-wasip2/predefined-macros.txt index 18a526b18..c22e628a3 100644 --- a/expected/wasm32-wasip2/predefined-macros.txt +++ b/expected/wasm32-wasip2/predefined-macros.txt @@ -53,6 +53,7 @@ #define ARG_MAX 131072 #define ARMAG "!\n" #define AT_EACCESS (0x0) +#define AT_EMPTY_PATH (0x8) #define AT_FDCWD (-2) #define AT_REMOVEDIR (0x4) #define AT_SYMLINK_FOLLOW (0x2) diff --git a/expected/wasm32-wasip3-coop/predefined-macros.txt b/expected/wasm32-wasip3-coop/predefined-macros.txt index 6afca371b..8ccb8607e 100644 --- a/expected/wasm32-wasip3-coop/predefined-macros.txt +++ b/expected/wasm32-wasip3-coop/predefined-macros.txt @@ -53,6 +53,7 @@ #define ARG_MAX 131072 #define ARMAG "!\n" #define AT_EACCESS (0x0) +#define AT_EMPTY_PATH (0x8) #define AT_FDCWD (-2) #define AT_REMOVEDIR (0x4) #define AT_SYMLINK_FOLLOW (0x2) diff --git a/expected/wasm32-wasip3/predefined-macros.txt b/expected/wasm32-wasip3/predefined-macros.txt index a4aeb5959..b13f037ac 100644 --- a/expected/wasm32-wasip3/predefined-macros.txt +++ b/expected/wasm32-wasip3/predefined-macros.txt @@ -53,6 +53,7 @@ #define ARG_MAX 131072 #define ARMAG "!\n" #define AT_EACCESS (0x0) +#define AT_EMPTY_PATH (0x8) #define AT_FDCWD (-2) #define AT_REMOVEDIR (0x4) #define AT_SYMLINK_FOLLOW (0x2) diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c b/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c index da52634a8..878652a1b 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c @@ -20,6 +20,15 @@ int __wasilibc_nocwd_fstatat(int fd, const char *restrict path, struct stat *restrict buf, int flag) { + if (path[0] == '\0') { + // With AT_EMPTY_PATH an empty path refers to fd itself; otherwise the + // WASI path-relative stat calls cannot resolve it, so reject as ENOENT. + if (flag & AT_EMPTY_PATH) + return fstat(fd, buf); + errno = ENOENT; + return -1; + } + #if defined(__wasip1__) __wasi_lookupflags_t lookup_flags = 0; if ((flag & AT_SYMLINK_NOFOLLOW) == 0) diff --git a/libc-bottom-half/headers/public/__header_fcntl.h b/libc-bottom-half/headers/public/__header_fcntl.h index d4170fe5b..51c76d6ad 100644 --- a/libc-bottom-half/headers/public/__header_fcntl.h +++ b/libc-bottom-half/headers/public/__header_fcntl.h @@ -63,6 +63,7 @@ #define AT_SYMLINK_NOFOLLOW (0x1) #define AT_SYMLINK_FOLLOW (0x2) #define AT_REMOVEDIR (0x4) +#define AT_EMPTY_PATH (0x8) #define AT_FDCWD (-2) diff --git a/libc-bottom-half/sources/posix.c b/libc-bottom-half/sources/posix.c index 47761d731..ed1cfadb9 100644 --- a/libc-bottom-half/sources/posix.c +++ b/libc-bottom-half/sources/posix.c @@ -93,6 +93,11 @@ ssize_t readlink(const char *restrict path, char *restrict buf, } int stat(const char *restrict path, struct stat *restrict buf) { + if (path[0] == '\0') { + errno = ENOENT; + return -1; + } + char *relative_path; int dirfd = find_relpath(path, &relative_path); @@ -106,6 +111,11 @@ int stat(const char *restrict path, struct stat *restrict buf) { } int lstat(const char *restrict path, struct stat *restrict buf) { + if (path[0] == '\0') { + errno = ENOENT; + return -1; + } + char *relative_path; int dirfd = find_relpath(path, &relative_path); @@ -387,6 +397,11 @@ int __wasilibc_utimens(const char *path, const struct timespec times[2], // Like `stat`, but with `fstatat`'s flags argument. int __wasilibc_stat(const char *__restrict path, struct stat *__restrict st, int flags) { + if (path[0] == '\0') { + errno = ENOENT; + return -1; + } + char *relative_path; int dirfd = find_relpath(path, &relative_path); diff --git a/test/src/stat.c b/test/src/stat.c index bed9b4663..f6406afb3 100644 --- a/test/src/stat.c +++ b/test/src/stat.c @@ -2,6 +2,7 @@ #include "test.h" #include +#include #include #include #include @@ -27,6 +28,20 @@ int main(void) { FILE *f; time_t t; + TEST(stat("", &st) == -1 && errno == ENOENT, "errno = %s\n", strerror(errno)); + TEST(lstat("", &st) == -1 && errno == ENOENT, "errno = %s\n", + strerror(errno)); + int dirfd = open(".", O_RDONLY); + if (TEST(dirfd >= 0, "errno = %s\n", strerror(errno))) { + TEST(fstatat(dirfd, "", &st, 0) == -1 && errno == ENOENT, "errno = %s\n", + strerror(errno)); + if (TEST(fstatat(dirfd, "", &st, AT_EMPTY_PATH) == 0, "errno = %s\n", + strerror(errno))) { + TEST(S_ISDIR(st.st_mode), "\n"); + } + close(dirfd); + } + if (TEST(stat(".", &st) == 0, "errno = %s\n", strerror(errno))) { TEST(S_ISDIR(st.st_mode), "\n"); TEST(st.st_nlink > 0, "%ju\n", (uintmax_t)st.st_nlink);