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:
@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user