mirror of
https://github.com/bytecodealliance/wasm-micro-runtime.git
synced 2025-03-11 16:35:33 +00:00
Enable WASI tests on Windows CI (#2699)
Most of the WASI filesystem tests require at least creating/deleting a file to test filesystem functionality so some additional filesystem APIs have been implemented on Windows so we can test what has been implemented so far. For those WASI functions which haven't been implemented, we skip the tests. These will be implemented in a future PR after which we can remove the relevant filters. Additionally, in order to run the WASI socket and thread tests, we need to install the wasi-sdk in CI and build the test source code prior to running the tests.
This commit is contained in:
parent
3624895204
commit
13875f43c6
24
.github/workflows/compilation_on_windows.yml
vendored
24
.github/workflows/compilation_on_windows.yml
vendored
|
@ -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
|
||||
|
|
3
core/iwasm/libraries/lib-socket/test/manifest.json
Normal file
3
core/iwasm/libraries/lib-socket/test/manifest.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "WAMR lib-socket tests"
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'\\'))
|
||||
{
|
||||
// \??\<drive>:\
|
||||
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\<server>\<share>\ - make sure the final path looks like \\<server>\<share>\
|
||||
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 \??\<drive>:\ 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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
51
product-mini/platforms/windows/wasi_filtered_tests.json
Normal file
51
product-mini/platforms/windows/wasi_filtered_tests.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user