diff --git a/.github/workflows/compilation_on_windows.yml b/.github/workflows/compilation_on_windows.yml index 3805dac3..97a4aaae 100644 --- a/.github/workflows/compilation_on_windows.yml +++ b/.github/workflows/compilation_on_windows.yml @@ -44,6 +44,10 @@ env: DEFAULT_TEST_OPTIONS: "-s spec -b" MULTI_MODULES_TEST_OPTIONS: "-s spec -b -M" THREADS_TEST_OPTIONS: "-s spec -b -p" + WASI_TEST_OPTIONS: "-s wasi_certification -w" + WASI_TEST_FILTER: ${{ github.workspace }}/product-mini/platforms/windows/wasi_filtered_tests.json + # Used when building the WASI socket and thread tests + CC: ${{ github.workspace }}/wasi-sdk/bin/clang # Cancel any in-flight jobs for the same PR/branch so there's only one active # at a time @@ -100,11 +104,31 @@ jobs: $DEFAULT_TEST_OPTIONS, $MULTI_MODULES_TEST_OPTIONS, $THREADS_TEST_OPTIONS, + $WASI_TEST_OPTIONS, ] steps: - name: checkout uses: actions/checkout@v3 + - name: download and install wasi-sdk + if: matrix.test_option == '$WASI_TEST_OPTIONS' + run: | + curl "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0.m-mingw.tar.gz" -o wasi-sdk.tar.gz -L + mkdir wasi-sdk + tar -xzf wasi-sdk.tar.gz -C wasi-sdk --strip-components 1 + + - name: build socket api tests + shell: bash + if: matrix.test_option == '$WASI_TEST_OPTIONS' + run: ./build.sh + working-directory: ./core/iwasm/libraries/lib-socket/test/ + + - name: Build WASI thread tests + shell: bash + if: matrix.test_option == '$WASI_TEST_OPTIONS' + run: ./build.sh + working-directory: ./core/iwasm/libraries/lib-wasi-threads/test/ + - name: run tests shell: bash timeout-minutes: 20 diff --git a/core/iwasm/libraries/lib-socket/test/manifest.json b/core/iwasm/libraries/lib-socket/test/manifest.json new file mode 100644 index 00000000..b0afd1d6 --- /dev/null +++ b/core/iwasm/libraries/lib-socket/test/manifest.json @@ -0,0 +1,3 @@ +{ + "name": "WAMR lib-socket tests" +} \ No newline at end of file diff --git a/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/src/posix.c b/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/src/posix.c index 8d9ac11e..c43423dc 100644 --- a/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/src/posix.c +++ b/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/src/posix.c @@ -1288,6 +1288,11 @@ path_get(wasm_exec_env_t exec_env, struct fd_table *curfds, size_t expansions = 0; char *symlink = NULL; size_t symlink_len; +#ifdef BH_PLATFORM_WINDOWS +#define PATH_SEPARATORS "/\\" +#else +#define PATH_SEPARATORS "/" +#endif for (;;) { // Extract the next pathname component from 'paths[curpath]', null @@ -1295,9 +1300,10 @@ path_get(wasm_exec_env_t exec_env, struct fd_table *curfds, // whether the pathname component is followed by one or more // trailing slashes, as this requires it to be a directory. char *file = paths[curpath]; - char *file_end = file + strcspn(file, "/"); - paths[curpath] = file_end + strspn(file_end, "/"); - bool ends_with_slashes = *file_end == '/'; + char *file_end = file + strcspn(file, PATH_SEPARATORS); + paths[curpath] = file_end + strspn(file_end, PATH_SEPARATORS); + bool ends_with_slashes = + (*file_end != '\0' && strchr(PATH_SEPARATORS, *file_end)); *file_end = '\0'; // Test for empty pathname strings and absolute paths. @@ -2880,8 +2886,7 @@ __wasi_errno_t wasmtime_ssp_sched_yield(void) { #ifdef BH_PLATFORM_WINDOWS - if (!SwitchToThread()) - return __WASI_EAGAIN; + SwitchToThread(); #else if (sched_yield() < 0) return convert_errno(errno); diff --git a/core/shared/platform/windows/platform_internal.h b/core/shared/platform/windows/platform_internal.h index 9d1937a4..275ea5d3 100644 --- a/core/shared/platform/windows/platform_internal.h +++ b/core/shared/platform/windows/platform_internal.h @@ -146,8 +146,19 @@ typedef enum windows_access_mode { windows_access_mode_write = 1 << 1 } windows_access_mode; +// These enum values are defined to be the same as the corresponding WASI +// fdflags so they can be used interchangeably. +typedef enum windows_fdflags { + windows_fdflags_append = 1 << 0, + windows_fdflags_dsync = 1 << 1, + windows_fdflags_nonblock = 1 << 2, + windows_fdflags_rsync = 1 << 3, + windows_fdflags_sync = 1 << 4 +} windows_fdflags; + typedef struct windows_handle { windows_handle_type type; + windows_fdflags fdflags; windows_access_mode access_mode; union { HANDLE handle; diff --git a/core/shared/platform/windows/win_file.c b/core/shared/platform/windows/win_file.c index 92003fc8..333e089e 100644 --- a/core/shared/platform/windows/win_file.c +++ b/core/shared/platform/windows/win_file.c @@ -7,6 +7,10 @@ #include "libc_errno.h" #include "win_util.h" +#include "PathCch.h" + +#pragma comment(lib, "Pathcch.lib") + #define CHECK_VALID_HANDLE_WITH_RETURN_VALUE(win_handle, ret) \ do { \ if ((win_handle) == NULL \ @@ -188,6 +192,79 @@ get_handle_filepath(HANDLE handle, wchar_t *buf, DWORD buf_size) return __WASI_ESUCCESS; } +static __wasi_errno_t +convert_hresult_error_code(HRESULT error_code) +{ + switch (error_code) { + case E_OUTOFMEMORY: + return __WASI_ENOMEM; + case E_INVALIDARG: + default: + return __WASI_EINVAL; + } +} + +// Returns the absolute filepath from the relative path to the directory +// associated with the provided handle. +static __wasi_errno_t +get_absolute_filepath(HANDLE handle, const char *relative_path, + wchar_t *absolute_path, size_t buf_len) +{ + wchar_t handle_path[PATH_MAX]; + + __wasi_errno_t error = get_handle_filepath(handle, handle_path, PATH_MAX); + + if (error != __WASI_ESUCCESS) + return error; + + wchar_t relative_wpath[PATH_MAX]; + error = convert_to_wchar(relative_path, relative_wpath, PATH_MAX); + + if (error != __WASI_ESUCCESS) + return error; + + HRESULT ret = + PathCchCombine(absolute_path, buf_len, handle_path, relative_wpath); + if (ret != S_OK) + error = convert_hresult_error_code(ret); + + return error; +} + +static bool +has_directory_attribute(DWORD attributes) +{ + if (attributes == INVALID_FILE_ATTRIBUTES) + return false; + + return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +static bool +is_directory(const wchar_t *path) +{ + DWORD attributes = GetFileAttributesW(path); + + return has_directory_attribute(attributes); +} + +static bool +has_symlink_attribute(DWORD attributes) +{ + if (attributes == INVALID_FILE_ATTRIBUTES) + return false; + + return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; +} + +static bool +is_symlink(const wchar_t *path) +{ + DWORD attributes = GetFileAttributesW(path); + + return has_symlink_attribute(attributes); +} + static void init_dir_stream(os_dir_stream dir_stream, os_file_handle handle) { @@ -275,17 +352,17 @@ create_handle(wchar_t *path, bool is_dir, bool follow_symlink, bool readonly) DWORD desired_access = GENERIC_READ; - if (!readonly) { + if (!readonly) desired_access |= GENERIC_WRITE; + else create_params.dwFileAttributes |= FILE_ATTRIBUTE_READONLY; - } return CreateFile2(path, desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OPEN_EXISTING, &create_params); } -#if WINAPI_PARTITION_DESKTOP +#if WINAPI_PARTITION_DESKTOP == 0 // Modifies the given path in place and replaces it with the filename component // (including the extension) of the path. static __wasi_errno_t @@ -387,6 +464,7 @@ get_disk_file_information(HANDLE handle, __wasi_filestat_t *buf) windows_handle dir_handle = { .access_mode = windows_access_mode_read, .raw = { .handle = raw_dir_handle }, + .fdflags = 0, .type = windows_handle_type_file }; windows_dir_stream dir_stream; init_dir_stream(&dir_stream, &dir_handle); @@ -484,7 +562,7 @@ get_disk_file_information(HANDLE handle, __wasi_filestat_t *buf) return error; } -#endif /* end of !WINAPI_PARTITION_DESKTOP */ +#endif /* end of WINAPI_PARTITION_DESKTOP == 0 */ static __wasi_errno_t get_file_information(os_file_handle handle, __wasi_filestat_t *buf) @@ -536,7 +614,8 @@ os_file_get_fdflags(os_file_handle handle, __wasi_fdflags_t *flags) { CHECK_VALID_HANDLE(handle); - return __WASI_ENOSYS; + *flags = handle->fdflags; + return __WASI_ESUCCESS; } __wasi_errno_t @@ -605,6 +684,7 @@ os_open_preopendir(const char *path, os_file_handle *out) (*out)->type = windows_handle_type_file; (*out)->raw.handle = dir_handle; + (*out)->fdflags = 0; (*out)->access_mode = windows_access_mode_read; return error; @@ -616,8 +696,135 @@ os_openat(os_file_handle handle, const char *path, __wasi_oflags_t oflags, wasi_libc_file_access_mode access_mode, os_file_handle *out) { CHECK_VALID_FILE_HANDLE(handle); + *out = BH_MALLOC(sizeof(windows_handle)); - return __WASI_ENOSYS; + if (*out == NULL) + return __WASI_ENOMEM; + + (*out)->type = windows_handle_type_file; + (*out)->fdflags = fs_flags; + (*out)->raw.handle = INVALID_HANDLE_VALUE; + + DWORD attributes = FILE_FLAG_BACKUP_SEMANTICS; + + if ((fs_flags & (__WASI_FDFLAG_SYNC | __WASI_FDFLAG_RSYNC)) != 0) + attributes |= (FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING); + if ((fs_flags & __WASI_FDFLAG_DSYNC) != 0) + attributes |= FILE_FLAG_WRITE_THROUGH; + + if ((oflags & __WASI_O_DIRECTORY) != 0) { + attributes |= FILE_ATTRIBUTE_DIRECTORY; + oflags &= ~(__WASI_O_DIRECTORY); + } + // Use async operations on the handle if it's not a directory + else { + attributes |= FILE_FLAG_OVERLAPPED; + } + + __wasi_errno_t error = __WASI_ESUCCESS; + + DWORD access_flags = 0; + if ((fs_flags & __WASI_FDFLAG_APPEND) != 0) { + if ((attributes & (FILE_FLAG_NO_BUFFERING)) != 0) { + // FILE_APPEND_DATA and FILE_FLAG_NO_BUFFERING are mutually + // exclusive - CreateFile2 returns 87 (invalid parameter) when they + // are combined. + error = __WASI_ENOTSUP; + goto fail; + } + access_flags |= FILE_APPEND_DATA; + } + + switch (access_mode) { + case WASI_LIBC_ACCESS_MODE_READ_ONLY: + access_flags |= GENERIC_READ; + (*out)->access_mode = windows_access_mode_read; + break; + case WASI_LIBC_ACCESS_MODE_WRITE_ONLY: + access_flags |= GENERIC_WRITE; + (*out)->access_mode = windows_access_mode_write; + break; + case WASI_LIBC_ACCESS_MODE_READ_WRITE: + access_flags |= GENERIC_WRITE | GENERIC_READ; + (*out)->access_mode = + windows_access_mode_read | windows_access_mode_write; + break; + } + + DWORD creation_disposition = 0; + + switch (oflags) { + case __WASI_O_CREAT | __WASI_O_EXCL: + case __WASI_O_CREAT | __WASI_O_EXCL | __WASI_O_TRUNC: + creation_disposition = CREATE_NEW; + break; + case __WASI_O_CREAT | __WASI_O_TRUNC: + creation_disposition = CREATE_ALWAYS; + break; + case __WASI_O_CREAT: + creation_disposition = OPEN_ALWAYS; + break; + case 0: + case __WASI_O_EXCL: + creation_disposition = OPEN_EXISTING; + break; + case __WASI_O_TRUNC: + case __WASI_O_EXCL | __WASI_O_TRUNC: + creation_disposition = TRUNCATE_EXISTING; + // CreateFile2 requires write access if we truncate the file upon + // opening + access_flags |= GENERIC_WRITE; + break; + } + + wchar_t absolute_path[PATH_MAX]; + error = get_absolute_filepath(handle->raw.handle, path, absolute_path, + PATH_MAX); + + if (error != __WASI_ESUCCESS) + goto fail; + + if ((lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW) == 0) + attributes |= FILE_FLAG_OPEN_REPARSE_POINT; + + // Check that we're not trying to open an existing file as a directory. + // Windows doesn't seem to throw an error in this case so add an + // explicit check. + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 + && creation_disposition == OPEN_EXISTING + && !is_directory(absolute_path)) { + error = __WASI_ENOTDIR; + goto fail; + } + + CREATEFILE2_EXTENDED_PARAMETERS create_params; + create_params.dwSize = sizeof(create_params); + create_params.dwFileAttributes = attributes & 0xFFF; + create_params.dwFileFlags = attributes & 0xFFF00000; + create_params.dwSecurityQosFlags = 0; + create_params.lpSecurityAttributes = NULL; + create_params.hTemplateFile = NULL; + + (*out)->raw.handle = + CreateFile2(absolute_path, access_flags, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + creation_disposition, &create_params); + + if ((*out)->raw.handle == INVALID_HANDLE_VALUE) { + error = convert_windows_error_code(GetLastError()); + goto fail; + } + + return error; +fail: + if (*out != NULL) { + if ((*out)->raw.handle != INVALID_HANDLE_VALUE) + CloseHandle((*out)->raw.handle); + + BH_FREE(*out); + } + + return error; } __wasi_errno_t @@ -656,13 +863,79 @@ os_close(os_file_handle handle, bool is_stdio) return __WASI_ESUCCESS; } +static __wasi_errno_t +read_data_at_offset(HANDLE handle, const struct __wasi_iovec_t *iov, int iovcnt, + __wasi_filesize_t offset, size_t *nwritten) +{ + OVERLAPPED *read_operations = + BH_MALLOC((uint32_t)(sizeof(OVERLAPPED) * (uint32_t)iovcnt)); + + if (read_operations == NULL) + return __WASI_ENOMEM; + + ULARGE_INTEGER query_offset = { .QuadPart = offset }; + __wasi_errno_t error = __WASI_ESUCCESS; + size_t total_bytes_read = 0; + + const __wasi_iovec_t *current = iov; + int successful_read_count = 0; + + for (int i = 0; i < iovcnt; ++i, ++current) { + read_operations[i].Internal = 0; + read_operations[i].InternalHigh = 0; + read_operations[i].Offset = query_offset.LowPart; + read_operations[i].OffsetHigh = query_offset.HighPart; + read_operations[i].hEvent = NULL; + + if (!ReadFileEx(handle, current->buf, (DWORD)current->buf_len, + &read_operations[i], NULL)) { + DWORD win_error = GetLastError(); + if (win_error != ERROR_IO_PENDING) { + error = convert_windows_error_code(win_error); + break; + } + } + ++successful_read_count; + query_offset.QuadPart += (DWORD)current->buf_len; + } + + // Get the result of all the asynchronous read operations + for (int i = 0; i < successful_read_count; ++i) { + DWORD bytes_transferred = 0; + if (!GetOverlappedResult(handle, &read_operations[i], + &bytes_transferred, true)) { + DWORD win_error = GetLastError(); + + if (win_error != ERROR_HANDLE_EOF) + error = convert_windows_error_code(win_error); + else + total_bytes_read += (size_t)bytes_transferred; + + CancelIo(handle); + + for (int j = i + 1; j < iovcnt; ++j) { + GetOverlappedResult(handle, &read_operations[j], + &bytes_transferred, true); + } + break; + } + + total_bytes_read += (size_t)bytes_transferred; + } + + *nwritten = total_bytes_read; + + BH_FREE(read_operations); + return error; +} + __wasi_errno_t os_preadv(os_file_handle handle, const struct __wasi_iovec_t *iov, int iovcnt, __wasi_filesize_t offset, size_t *nread) { CHECK_VALID_FILE_HANDLE(handle); - return __WASI_ENOSYS; + return read_data_at_offset(handle->raw.handle, iov, iovcnt, offset, nread); } __wasi_errno_t @@ -671,7 +944,90 @@ os_readv(os_file_handle handle, const struct __wasi_iovec_t *iov, int iovcnt, { CHECK_VALID_HANDLE(handle); - return __WASI_ENOSYS; + LARGE_INTEGER current_offset = { .QuadPart = 0 }; + + // Seek to the current offset before reading + int ret = SetFilePointerEx(handle->raw.handle, current_offset, + ¤t_offset, FILE_CURRENT); + if (ret == 0) + return convert_windows_error_code(GetLastError()); + + __wasi_errno_t error = + read_data_at_offset(handle->raw.handle, iov, iovcnt, + (__wasi_filesize_t)current_offset.QuadPart, nread); + + if (error != __WASI_ESUCCESS) + return error; + + current_offset.QuadPart += (LONGLONG)(*nread); + + // Update the current offset to match how many bytes we've read + ret = + SetFilePointerEx(handle->raw.handle, current_offset, NULL, FILE_BEGIN); + + if (ret == 0) + error = convert_windows_error_code(GetLastError()); + + return error; +} + +static __wasi_errno_t +write_data_at_offset(HANDLE handle, const struct __wasi_ciovec_t *iov, + int iovcnt, __wasi_filesize_t offset, size_t *nwritten) +{ + OVERLAPPED *write_operations = + BH_MALLOC((uint32_t)(sizeof(OVERLAPPED) * (uint32_t)iovcnt)); + + if (write_operations == NULL) + return __WASI_ENOMEM; + + ULARGE_INTEGER query_offset = { .QuadPart = offset }; + __wasi_errno_t error = __WASI_ESUCCESS; + size_t total_bytes_written = 0; + + const __wasi_ciovec_t *current = iov; + int successful_write_count = 0; + for (int i = 0; i < iovcnt; ++i, ++current) { + write_operations[i].Internal = 0; + write_operations[i].InternalHigh = 0; + write_operations[i].Offset = query_offset.LowPart; + write_operations[i].OffsetHigh = query_offset.HighPart; + write_operations[i].hEvent = NULL; + + if (!WriteFileEx(handle, current->buf, (DWORD)current->buf_len, + &write_operations[i], NULL)) { + DWORD win_error = GetLastError(); + if (win_error != ERROR_IO_PENDING) { + error = convert_windows_error_code(win_error); + break; + } + } + ++successful_write_count; + query_offset.QuadPart += (DWORD)current->buf_len; + } + + // Get the result of all the asynchronous writes + for (int i = 0; i < successful_write_count; ++i) { + DWORD bytes_transferred = 0; + if (!GetOverlappedResult(handle, &write_operations[i], + &bytes_transferred, true)) { + error = convert_windows_error_code(GetLastError()); + CancelIo(handle); + + for (int j = i + 1; j < iovcnt; ++j) { + GetOverlappedResult(handle, &write_operations[j], + &bytes_transferred, true); + } + break; + } + + total_bytes_written += (size_t)bytes_transferred; + } + + *nwritten = total_bytes_written; + + BH_FREE(write_operations); + return error; } __wasi_errno_t @@ -680,7 +1036,8 @@ os_pwritev(os_file_handle handle, const struct __wasi_ciovec_t *iov, int iovcnt, { CHECK_VALID_FILE_HANDLE(handle); - return __WASI_ENOSYS; + return write_data_at_offset(handle->raw.handle, iov, iovcnt, offset, + nwritten); } __wasi_errno_t @@ -689,7 +1046,31 @@ os_writev(os_file_handle handle, const struct __wasi_ciovec_t *iov, int iovcnt, { CHECK_VALID_HANDLE(handle); - return __WASI_ENOSYS; + bool append = (handle->fdflags & windows_fdflags_append) != 0; + LARGE_INTEGER write_offset = { .QuadPart = 0 }; + DWORD move_method = append ? FILE_END : FILE_CURRENT; + + int ret = SetFilePointerEx(handle->raw.handle, write_offset, &write_offset, + move_method); + if (ret == 0) + return convert_windows_error_code(GetLastError()); + + __wasi_errno_t error = write_data_at_offset( + handle->raw.handle, iov, iovcnt, + (__wasi_filesize_t)write_offset.QuadPart, nwritten); + + if (error != __WASI_ESUCCESS) + return error; + + write_offset.QuadPart += (LONGLONG)(*nwritten); + + // Update the write offset to match how many bytes we've written + ret = SetFilePointerEx(handle->raw.handle, write_offset, NULL, FILE_BEGIN); + + if (ret == 0) + error = convert_windows_error_code(GetLastError()); + + return error; } __wasi_errno_t @@ -735,7 +1116,151 @@ os_readlinkat(os_file_handle handle, const char *path, char *buf, { CHECK_VALID_FILE_HANDLE(handle); - return __WASI_ENOSYS; + wchar_t symlink_path[PATH_MAX]; + __wasi_errno_t error = + get_absolute_filepath(handle->raw.handle, path, symlink_path, PATH_MAX); + + if (error != __WASI_ESUCCESS) + return error; + + DWORD symlink_attributes = GetFileAttributesW(symlink_path); + + if (!has_symlink_attribute(symlink_attributes)) + return __WASI_EINVAL; + + HANDLE link_handle = create_handle( + symlink_path, has_directory_attribute(symlink_attributes), false, true); + + if (link_handle == INVALID_HANDLE_VALUE) + return convert_windows_error_code(GetLastError()); + +#if WINAPI_PARTITION_DESKTOP != 0 +// MinGW32 already has a definition for REPARSE_DATA_BUFFER +#if defined(_MSC_VER) || defined(__MINGW64_VERSION_MAJOR) + // See + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer + // for more details. + typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; + } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; +#endif + + char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + + REPARSE_DATA_BUFFER *reparse_data = (REPARSE_DATA_BUFFER *)buffer; + + if (!DeviceIoControl(link_handle, FSCTL_GET_REPARSE_POINT, NULL, 0, &buffer, + sizeof(buffer), NULL, NULL)) { + error = convert_windows_error_code(GetLastError()); + goto fail; + } + + int wbufsize = 0; + wchar_t *wbuf = NULL; + + // The following checks are taken from the libuv windows filesystem + // implementation, + // https://github.com/libuv/libuv/blob/v1.x/src/win/fs.c#L181-L244. Real + // symlinks can contain pretty much anything, but the only thing we really + // care about is undoing the implicit conversion to an NT namespaced path + // that CreateSymbolicLink will perform on absolute paths. + if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + wbuf = reparse_data->SymbolicLinkReparseBuffer.PathBuffer + + (reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset + / sizeof(wchar_t)); + wbufsize = reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength + / sizeof(wchar_t); + + if (wbufsize >= 4 && wbuf[0] == L'\\' && wbuf[1] == L'?' + && wbuf[2] == L'?' && wbuf[3] == L'\\') { + // Starts with \??\ + if (wbufsize >= 6 + && ((wbuf[4] >= L'A' && wbuf[4] <= L'Z') + || (wbuf[4] >= L'a' && wbuf[4] <= L'z')) + && wbuf[5] == L':' && (wbufsize == 6 || wbuf[6] == L'\\')) + { + // \??\:\ + wbuf += 4; + wbufsize -= 4; + } + else if (wbufsize >= 8 && (wbuf[4] == L'U' || wbuf[4] == L'u') + && (wbuf[5] == L'N' || wbuf[5] == L'n') + && (wbuf[6] == L'C' || wbuf[6] == L'c') + && wbuf[7] == L'\\') + { + // \??\UNC\\\ - make sure the final path looks like \\\\ + wbuf += 6; + wbuf[0] = L'\\'; + wbufsize -= 6; + } + } + } + else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { + // Junction + wbuf = reparse_data->MountPointReparseBuffer.PathBuffer + + (reparse_data->MountPointReparseBuffer.SubstituteNameOffset + / sizeof(wchar_t)); + wbufsize = reparse_data->MountPointReparseBuffer.SubstituteNameLength + / sizeof(wchar_t); + + // Only treat junctions that look like \??\:\ as a symlink. + if (!(wbufsize >= 6 && wbuf[0] == L'\\' && wbuf[1] == L'?' + && wbuf[2] == L'?' && wbuf[3] == L'\\' + && ((wbuf[4] >= L'A' && wbuf[4] <= L'Z') + || (wbuf[4] >= L'a' && wbuf[4] <= L'z')) + && wbuf[5] == L':' && (wbufsize == 6 || wbuf[6] == L'\\'))) { + error = __WASI_EINVAL; + goto fail; + } + + /* Remove leading \??\ */ + wbuf += 4; + wbufsize -= 4; + } + else { + error = __WASI_EINVAL; + goto fail; + } + + if (wbuf != NULL) + *nread = (size_t)WideCharToMultiByte(CP_UTF8, 0, wbuf, wbufsize, buf, + (int)bufsize, NULL, NULL); + + if (*nread == 0 && wbuf != NULL) { + DWORD win_error = GetLastError(); + if (win_error == ERROR_INSUFFICIENT_BUFFER) + *nread = bufsize; + else + error = convert_windows_error_code(win_error); + } +#else + error = __WASI_ENOTSUP; +#endif +fail: + CloseHandle(link_handle); + return error; } __wasi_errno_t @@ -762,7 +1287,19 @@ os_mkdirat(os_file_handle handle, const char *path) { CHECK_VALID_FILE_HANDLE(handle); - return __WASI_ENOSYS; + wchar_t absolute_path[PATH_MAX]; + __wasi_errno_t error = get_absolute_filepath(handle->raw.handle, path, + absolute_path, PATH_MAX); + + if (error != __WASI_ESUCCESS) + return error; + + bool success = CreateDirectoryW(absolute_path, NULL); + + if (!success) + error = convert_windows_error_code(GetLastError()); + + return error; } __wasi_errno_t @@ -780,7 +1317,28 @@ os_unlinkat(os_file_handle handle, const char *path, bool is_dir) { CHECK_VALID_FILE_HANDLE(handle); - return __WASI_ENOSYS; + wchar_t absolute_path[PATH_MAX]; + __wasi_errno_t error = get_absolute_filepath(handle->raw.handle, path, + absolute_path, PATH_MAX); + + if (error != __WASI_ESUCCESS) + return error; + + DWORD attributes = GetFileAttributesW(absolute_path); + + if (has_symlink_attribute(attributes)) { + // Override is_dir for symlinks. A symlink to a directory counts + // as a directory itself in Windows. + is_dir = has_directory_attribute(attributes); + } + + int ret = + is_dir ? RemoveDirectoryW(absolute_path) : DeleteFileW(absolute_path); + + if (ret == 0) + error = convert_windows_error_code(GetLastError()); + + return error; } __wasi_errno_t @@ -822,6 +1380,7 @@ create_stdio_handle(HANDLE raw_stdio_handle, DWORD stdio) stdio_handle->type = windows_handle_type_file; stdio_handle->access_mode = windows_access_mode_read | windows_access_mode_write; + stdio_handle->fdflags = 0; if (raw_stdio_handle == INVALID_HANDLE_VALUE) raw_stdio_handle = GetStdHandle(stdio); diff --git a/core/shared/platform/windows/win_socket.c b/core/shared/platform/windows/win_socket.c index d82cd724..bc05b001 100644 --- a/core/shared/platform/windows/win_socket.c +++ b/core/shared/platform/windows/win_socket.c @@ -70,6 +70,7 @@ os_socket_create(bh_socket_t *sock, bool is_ipv4, bool is_tcp) (*sock)->type = windows_handle_type_socket; (*sock)->access_mode = windows_access_mode_read | windows_access_mode_write; + (*sock)->fdflags = 0; if (is_ipv4) { af = AF_INET; @@ -174,6 +175,7 @@ os_socket_accept(bh_socket_t server_sock, bh_socket_t *sock, void *addr, (*sock)->type = windows_handle_type_socket; (*sock)->access_mode = windows_access_mode_read | windows_access_mode_write; + (*sock)->fdflags = 0; (*sock)->raw.socket = accept(server_sock->raw.socket, (struct sockaddr *)&addr_tmp, &len); diff --git a/product-mini/platforms/windows/wasi_filtered_tests.json b/product-mini/platforms/windows/wasi_filtered_tests.json new file mode 100644 index 00000000..492a746e --- /dev/null +++ b/product-mini/platforms/windows/wasi_filtered_tests.json @@ -0,0 +1,51 @@ +{ + "WASI C tests": { + "fdopendir-with-access": "Not implemented", + "lseek": "Not implemented" + }, + "WASI Rust tests": { + "dangling_symlink": "Not implemented", + "directory_seek": "Not implemented", + "dir_fd_op_failures": "Not implemented", + "fd_advise": "Not implemented", + "fd_fdstat_set_rights": "Not implemented", + "fd_filestat_set": "Not implemented", + "fd_flags_set": "Not implemented", + "fd_readdir": "Not implemented", + "file_allocate": "Not implemented", + "file_seek_tell": "Not implemented", + "file_truncation": "Not implemented", + "nofollow_errors": "Not implemented", + "path_exists": "Not implemented", + "path_filestat": "Not implemented", + "path_link": "Not implemented", + "path_open_preopen": "Not implemented", + "path_rename": "Not implemented", + "path_rename_dir_trailing_slashes": "Not implemented", + "path_symlink_trailing_slashes": "Not implemented", + "poll_oneoff_stdio": "Not implemented", + "readlink": "Not implemented (path_symlink)", + "symlink_create": "Not implemented", + "symlink_filestat": "Not implemented", + "symlink_loop": "Not implemented", + "truncation_rights": "Not implemented" + }, + "WASI threads proposal": { + "wasi_threads_exit_main_wasi_read": "Blocking ops not implemented", + "wasi_threads_exit_nonmain_wasi_read": "Blocking ops not implemented", + "wasi_threads_return_main_wasi_read": "Blocking ops not implemented", + "wasi_threads_return_main_wasi": "Blocking ops not implemented", + "wasi_threads_exit_nonmain_wasi": "Blocking ops not implemented", + "wasi_threads_exit_main_wasi": "Blocking ops not implemented" + }, + "WAMR lib-socket tests": { + "nslookup": "Not implemented", + "tcp_udp": "Not implemented" + }, + "lib-wasi-threads tests": { + "nonmain_proc_exit_sleep": "poll_oneoff not implemented", + "main_proc_exit_sleep": "poll_oneoff not implemented", + "main_trap_sleep": "poll_oneoff not implemented", + "nonmain_trap_sleep": "poll_oneoff not implemented" + } +} \ No newline at end of file diff --git a/tests/wamr-test-suites/test_wamr.sh b/tests/wamr-test-suites/test_wamr.sh index e30cf499..e23f7e68 100755 --- a/tests/wamr-test-suites/test_wamr.sh +++ b/tests/wamr-test-suites/test_wamr.sh @@ -566,7 +566,7 @@ function wasi_certification_test() cd wasi-testsuite git reset --hard ${WASI_TESTSUITE_COMMIT} - bash ../../wasi-test-script/run_wasi_tests.sh $1 $TARGET \ + bash ../../wasi-test-script/run_wasi_tests.sh $1 $TARGET $WASI_TEST_FILTER \ | tee -a ${REPORT_DIR}/wasi_test_report.txt ret=${PIPESTATUS[0]} @@ -836,6 +836,17 @@ function trigger() EXTRA_COMPILE_FLAGS+=" -DWAMR_BUILD_SANITIZER=tsan" fi + # Make sure we're using the builtin WASI libc implementation + # if we're running the wasi certification tests. + if [[ $TEST_CASE_ARR ]]; then + for test in "${TEST_CASE_ARR[@]}"; do + if [[ "$test" == "wasi_certification" ]]; then + EXTRA_COMPILE_FLAGS+=" -DWAMR_BUILD_LIBC_UVWASI=0 -DWAMR_BUILD_LIBC_WASI=1" + break + fi + done + fi + for t in "${TYPE[@]}"; do case $t in "classic-interp") diff --git a/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh b/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh index ae53f829..56d0a06b 100755 --- a/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh +++ b/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh @@ -9,6 +9,7 @@ THIS_DIR=$(cd $(dirname $0) && pwd -P) readonly MODE=$1 readonly TARGET=$2 +readonly TEST_FILTER=$3 readonly WORK_DIR=$PWD @@ -41,8 +42,21 @@ readonly THREAD_STRESS_TESTS="${WAMR_DIR}/core/iwasm/libraries/lib-wasi-threads/ readonly LIB_SOCKET_TESTS="${WAMR_DIR}/core/iwasm/libraries/lib-socket/test/" run_aot_tests () { - local tests=("$@") + local -n tests=$1 + local -n excluded_tests=$2 + for test_wasm in ${tests[@]}; do + # get the base file name from the filepath + local test_name=${test_wasm##*/} + test_name=${test_name%.wasm} + + for excluded_test in "${excluded_tests[@]}"; do + if [[ $excluded_test == "\"$test_name\"" ]]; then + echo "Skipping test $test_name" + continue 2 + fi + done + local iwasm="${IWASM_CMD}" if [[ $test_wasm =~ "stress" ]]; then iwasm="${IWASM_CMD_STRESS}" @@ -80,15 +94,21 @@ if [[ $MODE != "aot" ]];then $PYTHON_EXE -m pip install -r test-runner/requirements.txt export TEST_RUNTIME_EXE="${IWASM_CMD}" - $PYTHON_EXE ${THIS_DIR}/pipe.py | $PYTHON_EXE test-runner/wasi_test_runner.py \ - -r adapters/wasm-micro-runtime.py \ - -t \ - ${C_TESTS} \ - ${RUST_TESTS} \ - ${ASSEMBLYSCRIPT_TESTS} \ - ${THREAD_PROPOSAL_TESTS} \ - ${THREAD_INTERNAL_TESTS} \ - ${LIB_SOCKET_TESTS} \ + + TEST_OPTIONS="-r adapters/wasm-micro-runtime.py \ + -t \ + ${C_TESTS} \ + ${RUST_TESTS} \ + ${ASSEMBLYSCRIPT_TESTS} \ + ${THREAD_PROPOSAL_TESTS} \ + ${THREAD_INTERNAL_TESTS} \ + ${LIB_SOCKET_TESTS}" + + if [ -n "$TEST_FILTER" ]; then + TEST_OPTIONS="${TEST_OPTIONS} --exclude-filter ${TEST_FILTER}" + fi + + $PYTHON_EXE ${THIS_DIR}/pipe.py | $PYTHON_EXE test-runner/wasi_test_runner.py $TEST_OPTIONS ret=${PIPESTATUS[1]} @@ -114,7 +134,17 @@ else for testsuite in ${THREAD_STRESS_TESTS} ${THREAD_PROPOSAL_TESTS} ${THREAD_INTERNAL_TESTS}; do tests=$(ls ${testsuite}*.wasm) tests_array=($tests) - run_aot_tests "${tests_array[@]}" + + if [ -n "$TEST_FILTER" ]; then + readarray -t excluded_tests_array < <(jq -c \ + --slurpfile testsuite_manifest $testsuite/manifest.json \ + '.[$testsuite_manifest[0].name] // {} | keys[]' \ + $TEST_FILTER) + else + excluded_tests_array=() + fi + + run_aot_tests tests_array excluded_tests_array done fi