diff --git a/expected/wasm32-wasip2/defined-symbols.txt b/expected/wasm32-wasip2/defined-symbols.txt index f72ada382..16b9d5fcf 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -339,7 +339,6 @@ __wasilibc_open_nomode __wasilibc_populate_preopens __wasilibc_pthread_self __wasilibc_random -__wasilibc_register_preopened_fd __wasilibc_rename_newat __wasilibc_rename_oldat __wasilibc_reset_preopens diff --git a/libc-bottom-half/headers/public/wasi/libc.h b/libc-bottom-half/headers/public/wasi/libc.h index abd2040cd..096fad449 100644 --- a/libc-bottom-half/headers/public/wasi/libc.h +++ b/libc-bottom-half/headers/public/wasi/libc.h @@ -21,11 +21,8 @@ struct timespec; /// afterward, you should call this before doing so. void __wasilibc_populate_preopens(void); +#ifndef __wasilibc_use_wasip2 /// Register the given pre-opened file descriptor under the given path. -#ifdef __wasilibc_use_wasip2 -int __wasilibc_register_preopened_fd(filesystem_preopens_own_descriptor_t fd, - wasip2_string_t relprefix); -#else int __wasilibc_register_preopened_fd(int fd, const char* prefix); #endif diff --git a/libc-bottom-half/sources/preopens.c b/libc-bottom-half/sources/preopens.c index 07bd90116..27734a1f0 100644 --- a/libc-bottom-half/sources/preopens.c +++ b/libc-bottom-half/sources/preopens.c @@ -21,292 +21,21 @@ #include #ifdef __wasilibc_use_wasip2 -/// A name and file descriptor pair. -typedef struct preopen { - /// The path prefix associated with the file descriptor. - wasip2_string_t prefix; - - /// The file descriptor. - filesystem_preopens_own_descriptor_t fd; -} preopen; - -/// A simple growable array of `preopen`. -static _Atomic _Bool preopens_populated = false; -static preopen *preopens; -static size_t num_preopens; -static size_t preopen_capacity; - -/// Access to the the above preopen must be protected in the presence of -/// threads. -#ifdef _REENTRANT -static volatile int lock[1]; -#endif - -#ifdef NDEBUG -#define assert_invariants() // assertions disabled +typedef filesystem_preopens_own_descriptor_t preopen_t; #else -static void assert_invariants(void) { - assert(num_preopens <= preopen_capacity); - assert(preopen_capacity == 0 || preopens != NULL); - assert(preopen_capacity == 0 || - preopen_capacity * sizeof(preopen) > preopen_capacity); - - for (size_t i = 0; i < num_preopens; ++i) { - const preopen *pre = &preopens[i]; - } -} +typedef __wasi_fd_t preopen_t; #endif -/// Allocate space for more preopens. Returns 0 on success and -1 on failure. -static int resize(void) { - size_t start_capacity = 4; - size_t old_capacity = preopen_capacity; - size_t new_capacity = old_capacity == 0 ? start_capacity : old_capacity * 2; - - preopen *old_preopens = preopens; - preopen *new_preopens = calloc(sizeof(preopen), new_capacity); - if (new_preopens == NULL) { - return -1; - } - - memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen)); - preopens = new_preopens; - preopen_capacity = new_capacity; - free(old_preopens); - - assert_invariants(); - return 0; -} - -// Normalize an absolute path. Removes leading `/` and leading `./`, so the -// first character is the start of a directory name. This works because our -// process always starts with a working directory of `/`. Additionally translate -// `.` to the empty string. -static const char *strip_prefixes(const char *path) { - while (1) { - if (path[0] == '/') { - path++; - } else if (path[0] == '.' && path[1] == '/') { - path += 2; - } else if (path[0] == '.' && path[1] == 0) { - path++; - } else { - break; - } - } - - return path; -} - -/// Similar to `internal_register_preopened_fd` but does not take a lock. -static int internal_register_preopened_fd_unlocked(filesystem_preopens_own_descriptor_t fd, - wasip2_string_t relprefix) { - // Check preconditions. - assert_invariants(); - - if (num_preopens == preopen_capacity && resize() != 0) { - return -1; - } - - preopens[num_preopens++] = (preopen) { relprefix, fd, }; - - assert_invariants(); - return 0; -} - -/// Register the given preopened file descriptor under the given path. -/// -/// This function takes ownership of `prefix`. -static int internal_register_preopened_fd(filesystem_preopens_own_descriptor_t fd, - wasip2_string_t relprefix) { - LOCK(lock); - - int r = internal_register_preopened_fd_unlocked(fd, relprefix); - - UNLOCK(lock); - - return r; -} - -/// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`? -static bool prefix_matches(const uint8_t *prefix, size_t prefix_len, const char *path) { - // Allow an empty string as a prefix of any relative path. - if (path[0] != '/' && prefix_len == 0) - return true; - - // Allow a '/' as a prefix of any relative path. - if (path[0] != '/' && prefix_len == 1 && prefix[0] == '/') - return true; - - // Check whether any bytes of the prefix differ. - if (memcmp(path, prefix, prefix_len) != 0) - return false; - - // Ignore trailing slashes in directory names. - size_t i = prefix_len; - while (i > 0 && prefix[i - 1] == '/') { - --i; - } - - // Match only complete path components. - char last = path[i]; - return last == '/' || last == '\0'; -} - -// See the documentation in libc.h -int __wasilibc_register_preopened_fd(filesystem_preopens_own_descriptor_t fd, - wasip2_string_t prefix) { - __wasilibc_populate_preopens(); - - return internal_register_preopened_fd(fd, prefix); -} - -// See the documentation in libc-find-relpath.h. -int __wasilibc_find_relpath(const char *path, - const char **abs_prefix, - char **relative_path, - size_t relative_path_len) { - // If `chdir` is linked, whose object file defines this symbol, then we - // call that. Otherwise if the program can't `chdir` then `path` is - // absolute (or relative to the root dir), so we delegate to `find_abspath` - if (__wasilibc_find_relpath_alloc) - return __wasilibc_find_relpath_alloc(path, abs_prefix, relative_path, &relative_path_len, 0); - return __wasilibc_find_abspath(path, abs_prefix, (const char**) relative_path); -} - -// See the documentation in libc-find-relpath.h. -int __wasilibc_find_abspath(const char *path, - const char **abs_prefix, - const char **relative_path) { - __wasilibc_populate_preopens(); - - // Strip leading `/` characters, the prefixes we're matching won't have - // them. - while (*path == '/') - path++; - // Search through the preopens table. Iterate in reverse so that more - // recently added preopens take precedence over less recently addded ones. - size_t match_len = 0; - bool found_handle = false; - filesystem_preopens_own_descriptor_t handle; - LOCK(lock); - for (size_t i = num_preopens; i > 0; --i) { - const preopen *pre = &preopens[i - 1]; - const uint8_t *prefix = pre->prefix.ptr; - size_t len = pre->prefix.len; - - // If we haven't had a match yet, or the candidate path is longer than - // our current best match's path, and the candidate path is a prefix of - // the requested path, take that as the new best path. - if ((!found_handle || len > match_len) && - prefix_matches(prefix, len, path)) - { - found_handle = true; - handle = pre->fd; - match_len = len; - *abs_prefix = (const char*) prefix; - } - } - UNLOCK(lock); - - if (!found_handle) { - errno = ENOENT; - return -1; - } - - // The relative path is the substring after the portion that was matched. - const char *computed = path; - if (prefix_matches((const uint8_t*) *abs_prefix, match_len, path) - && !((*abs_prefix[0] == '/') && (path[0] != '/'))) - computed = path + match_len; - - if (path[0] == '/') - computed = path + match_len; - - // Omit leading slashes in the relative path. - while (*computed == '/') - ++computed; - - // *at syscalls don't accept empty relative paths, so use "." instead. - if (*computed == '\0') - computed = "."; - - *relative_path = computed; - int fd = -1; - descriptor_table_entry_t entry; - entry.tag = DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE; - entry.file.readable = true; - entry.file.writable = true; - entry.file.file_handle = filesystem_borrow_descriptor(handle); - if (!descriptor_table_insert(entry, &fd)) - return -1; - return fd; -} - -void __wasilibc_populate_preopens(void) { - // Fast path: If the preopens are already initialized, do nothing. - if (preopens_populated) { - return; - } - - LOCK(lock); - - // Check whether another thread initialized the preopens already. - if (preopens_populated) { - UNLOCK(lock); - return; - } - - filesystem_preopens_list_tuple2_own_descriptor_string_t preopens; - filesystem_preopens_get_directories(&preopens); - - for (size_t i = 0; i < preopens.len; ++i) { - filesystem_preopens_tuple2_own_descriptor_string_t name_and_descriptor = preopens.ptr[i]; - if (internal_register_preopened_fd_unlocked(name_and_descriptor.f0, - name_and_descriptor.f1)) { - goto software; - } - } - - // Preopens are now initialized. - preopens_populated = true; - - UNLOCK(lock); - - return; -// oserr: -// _Exit(EX_OSERR); -software: - _Exit(EX_SOFTWARE); -} - -void __wasilibc_reset_preopens(void) { - LOCK(lock); - - if (num_preopens) { - for (int i = 0; i < num_preopens; ++i) { - wasip2_string_free(&preopens[i].prefix); - } - free(preopens); - } - - preopens_populated = false; - preopens = NULL; - num_preopens = 0; - preopen_capacity = 0; - - assert_invariants(); - - UNLOCK(lock); -} -#else /// A name and file descriptor pair. typedef struct preopen { /// The path prefix associated with the file descriptor. - const char *prefix; + char *prefix; - /// The file descriptor. - __wasi_fd_t fd; +#ifdef __wasilibc_use_wasip2 + int libc_fd; +#else + preopen_t wasi_handle; +#endif } preopen; /// A simple growable array of `preopen`. @@ -333,7 +62,12 @@ static void assert_invariants(void) { for (size_t i = 0; i < num_preopens; ++i) { const preopen *pre = &preopens[i]; assert(pre->prefix != NULL); - assert(pre->fd != (__wasi_fd_t)-1); +#ifdef __wasilibc_use_wasip2 + assert(pre->wasi_handle.__handle != 0); + assert(pre->wasi_handle.libc_fd != -1); +#else + assert(pre->wasi_handle != -1); +#endif #ifdef __wasm__ assert((uintptr_t)pre->prefix < (__uint128_t)__builtin_wasm_memory_size(0) * PAGESIZE); @@ -384,38 +118,52 @@ static const char *strip_prefixes(const char *path) { } /// Similar to `internal_register_preopened_fd` but does not take a lock. -static int internal_register_preopened_fd_unlocked(__wasi_fd_t fd, const char *relprefix) { +static int internal_register_preopened_fd_unlocked(preopen_t fd, const char *relprefix) { // Check preconditions. assert_invariants(); +#ifdef __wasilibc_use_wasip2 + assert(fd.__handle != 0); +#else assert(fd != AT_FDCWD); assert(fd != -1); +#endif assert(relprefix != NULL); - if (num_preopens == preopen_capacity && resize() != 0) { - return -1; - } + if (num_preopens == preopen_capacity && resize() != 0) + goto err; char *prefix = strdup(strip_prefixes(relprefix)); - if (prefix == NULL) { - return -1; - } - preopens[num_preopens++] = (preopen) { prefix, fd, }; + if (prefix == NULL) + goto err; + preopens[num_preopens].prefix = prefix; +#ifdef __wasilibc_use_wasip2 + descriptor_table_entry_t entry; + entry.tag = DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE; + entry.file.readable = true; + entry.file.writable = true; + entry.file.file_handle = filesystem_borrow_descriptor(fd); + if (!descriptor_table_insert(entry, &preopens[num_preopens].libc_fd)) + goto err_free_prefix; + + assert(preopens[num_preopens].libc_fd != -1); +#else + preopens[num_preopens].wasi_handle = fd; +#endif + num_preopens++; assert_invariants(); return 0; -} - -/// Register the given preopened file descriptor under the given path. -/// -/// This function takes ownership of `prefix`. -static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) { - LOCK(lock); - int r = internal_register_preopened_fd_unlocked(fd, relprefix); - - UNLOCK(lock); +#ifdef __wasilibc_use_wasip2 +err_free_prefix: + free(prefix); +#endif - return r; +err: +#ifdef __wasilibc_use_wasip2 + filesystem_descriptor_drop_own(fd); +#endif + return -1; } /// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`? @@ -439,12 +187,27 @@ static bool prefix_matches(const char *prefix, size_t prefix_len, const char *pa return last == '/' || last == '\0'; } +#ifndef __wasilibc_use_wasip2 +/// Register the given preopened file descriptor under the given path. +/// +/// This function takes ownership of `prefix`. +static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) { + LOCK(lock); + + int r = internal_register_preopened_fd_unlocked(fd, relprefix); + + UNLOCK(lock); + + return r; +} + // See the documentation in libc.h int __wasilibc_register_preopened_fd(int fd, const char *prefix) { __wasilibc_populate_preopens(); return internal_register_preopened_fd((__wasi_fd_t)fd, prefix); } +#endif // See the documentation in libc-find-relpath.h. int __wasilibc_find_relpath(const char *path, @@ -485,7 +248,11 @@ int __wasilibc_find_abspath(const char *path, if ((fd == -1 || len > match_len) && prefix_matches(prefix, len, path)) { - fd = pre->fd; +#ifdef __wasilibc_use_wasip2 + fd = pre->libc_fd; +#else + fd = pre->wasi_handle; +#endif match_len = len; *abs_prefix = prefix; } @@ -526,6 +293,25 @@ void __wasilibc_populate_preopens(void) { return; } +#ifdef __wasilibc_use_wasip2 + filesystem_preopens_list_tuple2_own_descriptor_string_t preopens; + filesystem_preopens_get_directories(&preopens); + + for (size_t i = 0; i < preopens.len; ++i) { + filesystem_preopens_tuple2_own_descriptor_string_t name_and_descriptor = preopens.ptr[i]; + char *prefix = strndup((const char*) name_and_descriptor.f1.ptr, name_and_descriptor.f1.len); + wasip2_string_free(&name_and_descriptor.f1); + if (prefix == NULL) { + filesystem_descriptor_drop_own(name_and_descriptor.f0); + goto software; + } + int r = internal_register_preopened_fd_unlocked(name_and_descriptor.f0, prefix); + free(prefix); + if (r != 0) + goto software; + } + +#else // __wasilibc_use_wasip2 // Skip stdin, stdout, and stderr, and count up until we reach an invalid // file descriptor. for (__wasi_fd_t fd = 3; fd != 0; ++fd) { @@ -549,9 +335,10 @@ void __wasilibc_populate_preopens(void) { goto oserr; prefix[prestat.u.dir.pr_name_len] = '\0'; - if (internal_register_preopened_fd_unlocked(fd, prefix) != 0) - goto software; + int r = internal_register_preopened_fd_unlocked(fd, prefix); free(prefix); + if (r != 0) + goto software; break; } @@ -559,6 +346,7 @@ void __wasilibc_populate_preopens(void) { break; } } +#endif // not __wasilibc_use_wasip2 // Preopens are now initialized. preopens_populated = true; @@ -566,8 +354,10 @@ void __wasilibc_populate_preopens(void) { UNLOCK(lock); return; +#ifndef __wasilibc_use_wasip2 oserr: _Exit(EX_OSERR); +#endif software: _Exit(EX_SOFTWARE); } @@ -578,17 +368,19 @@ void __wasilibc_reset_preopens(void) { if (num_preopens) { for (int i = 0; i < num_preopens; ++i) { free((void*) preopens[i].prefix); +#ifdef __wasilibc_use_wasip2 + close(preopens[i].libc_fd); +#endif } free(preopens); } - + preopens_populated = false; preopens = NULL; num_preopens = 0; preopen_capacity = 0; - + assert_invariants(); - + UNLOCK(lock); } -#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2670e76d7..a58d50ab9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -264,6 +264,7 @@ if (NOT TARGET_TRIPLE MATCHES "-threads") endif() add_wasilibc_test(argv_two_args.c ARGV foo bar) add_wasilibc_test(clock_nanosleep.c) +add_wasilibc_test(chdir.c FS) add_wasilibc_test(external_env.c ENV VAR1=foo VAR2=bar) add_wasilibc_test(fadvise.c FS) add_wasilibc_test(fallocate.c FS) diff --git a/test/src/chdir.c b/test/src/chdir.c new file mode 100644 index 000000000..8c81ffb7e --- /dev/null +++ b/test/src/chdir.c @@ -0,0 +1,98 @@ +#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 exists(const char *path) { + struct stat st; + return stat(path, &st) == 0; +} + +static void spray_heap(void) { + #define N 100 + void *ptrs[N]; + for (int i = 0; i < N; i++) { + size_t size = (rand() % 256) + 16; + ptrs[i] = malloc(size); + memset(ptrs[i], 0x42, size); + } + for (int i = 0; i < N; i++) { + free(ptrs[i]); + } +} + +int main(void) { + spray_heap(); + + int fd; + TEST(mkdir("a", 0755) == 0); + TEST(mkdir("a/b", 0755) == 0); + TEST(mkdir("a/b/c", 0755) == 0); + + TEST((fd = open("a1.txt", O_CREAT | O_WRONLY, 0644)) >= 0); + TEST(close(fd) == 0); + TEST((fd = open("a/a2.txt", O_CREAT | O_WRONLY, 0644)) >= 0); + TEST(close(fd) == 0); + TEST((fd = open("a/b/a3.txt", O_CREAT | O_WRONLY, 0644)) >= 0); + TEST(close(fd) == 0); + TEST((fd = open("a/b/c/a4.txt", O_CREAT | O_WRONLY, 0644)) >= 0); + TEST(close(fd) == 0); + + TEST(exists("a1.txt")); + TEST(exists("a/a2.txt")); + TEST(exists("a/b/a3.txt")); + TEST(exists("a/b/c/a4.txt")); + + TEST(chdir("a") == 0); + TEST(exists("../a1.txt")); + TEST(exists("a2.txt")); + TEST(exists("b/a3.txt")); + TEST(exists("b/c/a4.txt")); + + TEST(chdir("b") == 0); + TEST(exists("../../a1.txt")); + TEST(exists("../a2.txt")); + TEST(exists("a3.txt")); + TEST(exists("c/a4.txt")); + + TEST(chdir("c") == 0); + TEST(exists("../../../a1.txt")); + TEST(exists("../../a2.txt")); + TEST(exists("../a3.txt")); + TEST(exists("a4.txt")); + + TEST(chdir("..") == 0); + TEST(exists("../../a1.txt")); + TEST(exists("../a2.txt")); + TEST(exists("a3.txt")); + TEST(exists("c/a4.txt")); + + TEST(chdir("..") == 0); + TEST(exists("../a1.txt")); + TEST(exists("a2.txt")); + TEST(exists("b/a3.txt")); + TEST(exists("b/c/a4.txt")); + + TEST(chdir("..") == 0); + TEST(exists("a1.txt")); + TEST(exists("a/a2.txt")); + TEST(exists("a/b/a3.txt")); + TEST(exists("a/b/c/a4.txt")); + + TEST(chdir("a/b") == 0); + TEST(exists("../../a1.txt")); + TEST(exists("../a2.txt")); + TEST(exists("a3.txt")); + TEST(exists("c/a4.txt")); + + return t_status; +}