From a74116f973d2dc1d8f8dd141fa41a4e0cc67f407 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 20 Mar 2026 12:46:57 -0600 Subject: [PATCH] clear descriptor table in `__wasilibc_reset_preopens` The original intention of `__wasilibc_reset_preopens` was to clear the stdio state as the last step of Wizer-style pre-initialization since none of the handles would be valid when the snapshot is resumed. In earlier versions of `wasi-libc`, which didn't have native p2 or p3 support for stdio or file I/O, resetting the WASIp1 state was sufficient; the `wasi_snapshot_preview1` adapter took care of emulating p1 in terms of p2. However, now that `wasi-libc` has native support for p2 and p3, we need to also reset the descriptor table. At this point, the name `__wasilibc_reset_preopens` is a bit misleading for p2+ since we're clearing more than just the preopens, but changing the name would break tools which use it, so probably not worth the trouble. --- expected/wasm32-wasip2/defined-symbols.txt | 3 +- expected/wasm32-wasip3/defined-symbols.txt | 3 +- .../headers/private/wasi/descriptor_table.h | 3 ++ libc-bottom-half/headers/public/wasi/libc.h | 7 ++++ libc-bottom-half/sources/descriptor_table.c | 21 ++++++++++++ libc-bottom-half/sources/preopens.c | 4 +++ test/CMakeLists.txt | 6 ++++ test/src/clear_fds.c | 33 +++++++++++++++++++ 8 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 test/src/clear_fds.c diff --git a/expected/wasm32-wasip2/defined-symbols.txt b/expected/wasm32-wasip2/defined-symbols.txt index 9996fa30a..6c465b9d6 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -522,6 +522,7 @@ ctanhl ctanl ctime ctime_r +descriptor_table_clear descriptor_table_get_ref descriptor_table_insert descriptor_table_remove @@ -1569,4 +1570,4 @@ y0f y1 y1f yn -ynf \ No newline at end of file +ynf diff --git a/expected/wasm32-wasip3/defined-symbols.txt b/expected/wasm32-wasip3/defined-symbols.txt index ac066e5bd..5c2399d36 100644 --- a/expected/wasm32-wasip3/defined-symbols.txt +++ b/expected/wasm32-wasip3/defined-symbols.txt @@ -523,6 +523,7 @@ ctanhl ctanl ctime ctime_r +descriptor_table_clear descriptor_table_get_ref descriptor_table_insert descriptor_table_remove @@ -1596,4 +1597,4 @@ y0f y1 y1f yn -ynf \ No newline at end of file +ynf diff --git a/libc-bottom-half/headers/private/wasi/descriptor_table.h b/libc-bottom-half/headers/private/wasi/descriptor_table.h index 7cdffb7e1..571ee4cfb 100644 --- a/libc-bottom-half/headers/private/wasi/descriptor_table.h +++ b/libc-bottom-half/headers/private/wasi/descriptor_table.h @@ -239,6 +239,9 @@ int descriptor_table_remove(int fd); /// errno on failure. int descriptor_table_renumber(int fd, int newfd); +/// Removes all file descriptors from the table, running their destructors. +void descriptor_table_clear(); + #endif // __wasip1__ #endif // DESCRIPTOR_TABLE_H diff --git a/libc-bottom-half/headers/public/wasi/libc.h b/libc-bottom-half/headers/public/wasi/libc.h index 577c636b8..383858c1d 100644 --- a/libc-bottom-half/headers/public/wasi/libc.h +++ b/libc-bottom-half/headers/public/wasi/libc.h @@ -19,6 +19,13 @@ struct timespec; /// afterward, you should call this before doing so. void __wasilibc_populate_preopens(void); +/// Clear the set of preopens (and, on p2+, any other open file descriptors). +/// +/// This is appropriate to call just prior to snapshotting the guest state so +/// that it can be resumed on another runtime, in which case any +/// previously-opened handles will no longer be valid. +void __wasilibc_reset_preopens(); + #ifndef __wasip2__ /// Register the given pre-opened file descriptor under the given path. int __wasilibc_register_preopened_fd(int fd, const char *prefix); diff --git a/libc-bottom-half/sources/descriptor_table.c b/libc-bottom-half/sources/descriptor_table.c index 0bca6a29a..61df01610 100644 --- a/libc-bottom-half/sources/descriptor_table.c +++ b/libc-bottom-half/sources/descriptor_table.c @@ -125,6 +125,22 @@ static int remove(descriptor_table_t *table, int fd, return 0; } +static void clear(descriptor_table_t *table) { + for (size_t i = 0; i < table->len; ++i) { + descriptor_table_item_t *table_entry = &table->entries[i]; + if (table_entry->occupied) { + descriptor_table_entry_t entry = table_entry->entry; + entry.vtable->free(entry.data); + } + } + if (table->entries) + free(table->entries); + table->entries = NULL; + table->next = 0; + table->len = 0; + table->cap = 0; +} + static bool stdio_initialized = false; static int init_stdio() { @@ -176,3 +192,8 @@ int descriptor_table_remove(int fd) { entry.vtable->free(entry.data); return 0; } + +void descriptor_table_clear() { + clear(&global_table); + stdio_initialized = false; +} diff --git a/libc-bottom-half/sources/preopens.c b/libc-bottom-half/sources/preopens.c index c10227cce..eaa76df31 100644 --- a/libc-bottom-half/sources/preopens.c +++ b/libc-bottom-half/sources/preopens.c @@ -418,5 +418,9 @@ void __wasilibc_reset_preopens(void) { assert_invariants(); +#ifndef __wasip1__ + descriptor_table_clear(); +#endif + UNLOCK(lock); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ceacfe615..22265d840 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -330,6 +330,12 @@ add_wasilibc_test(sleep.c) add_wasilibc_test(write.c FS) add_wasilibc_test(wasi-defines.c) +# This tests the behavior of the WASIp2+ descriptor table and won't succeed on +# p1: +if (NOT (WASI STREQUAL "p1")) + add_wasilibc_test(clear_fds.c FS) +endif() + if (CMAKE_C_COMPILER_VERSION VERSION_GREATER 20.0) add_wasilibc_test(setjmp.c SETJMP) set_tests_properties(setjmp.wasm PROPERTIES LABELS v8fail) diff --git a/test/src/clear_fds.c b/test/src/clear_fds.c new file mode 100644 index 000000000..5b24c4c20 --- /dev/null +++ b/test/src/clear_fds.c @@ -0,0 +1,33 @@ +#include "test.h" +#include +#include +#include +#include +#include +#include +#include + +#define TEST(c) \ + do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ + } while (0) + +int main(void) { + char tmp[] = "testsuite-XXXXXX"; + int fd; + TEST((fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6) == 6); + TEST(write(2, "test stderr\n", 12) == 12); + // This should clear the entire descriptor table, closing all descriptors + // (e.g. as the last step of a Wizer-style pre-init function). + __wasilibc_reset_preopens(); + if (!(write(fd, "hello", 6) == -1 && errno == EBADF)) + t_error("fd should have been closed by `__wasilibc_reset_preopens`"); + // stdio handles will be lazily reinitialized, so should still work: + TEST(write(2, "test stderr\n", 12) == 12); + if (fd > 2) + TEST(unlink(tmp) != -1); + return t_status; +}