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; +}