Overlapped I/O support (fixes ProDG compilers)

This commit is contained in:
Luke Street 2025-09-29 23:32:17 -06:00
parent cb154f3118
commit 62d8daccac
15 changed files with 767 additions and 124 deletions

View File

@ -45,6 +45,7 @@ add_executable(wibo
dll/user32.cpp
dll/vcruntime.cpp
dll/version.cpp
errors.cpp
files.cpp
handles.cpp
loader.cpp
@ -151,6 +152,17 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/test/test_heap.c
${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h)
add_custom_command(
OUTPUT ${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe
COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2
-I${CMAKE_CURRENT_SOURCE_DIR}/test
-o test_overlapped_io.exe
${CMAKE_CURRENT_SOURCE_DIR}/test/test_overlapped_io.c
WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/test/test_overlapped_io.c
${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h)
add_custom_target(wibo_test_fixtures
DEPENDS
${WIBO_TEST_BIN_DIR}/external_exports.dll
@ -158,7 +170,8 @@ if(BUILD_TESTING)
${WIBO_TEST_BIN_DIR}/test_bcrypt.exe
${WIBO_TEST_BIN_DIR}/test_resources.exe
${WIBO_TEST_BIN_DIR}/test_threading.exe
${WIBO_TEST_BIN_DIR}/test_heap.exe)
${WIBO_TEST_BIN_DIR}/test_heap.exe
${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe)
if(CMAKE_CONFIGURATION_TYPES)
set(_wibo_fixture_build_command
@ -199,6 +212,12 @@ if(BUILD_TESTING)
set_tests_properties(wibo.test_heap PROPERTIES
WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR}
DEPENDS wibo.build_fixtures)
add_test(NAME wibo.test_overlapped_io
COMMAND $<TARGET_FILE:wibo> ${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe)
set_tests_properties(wibo.test_overlapped_io PROPERTIES
WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR}
DEPENDS wibo.build_fixtures)
endif()
endif()
endif()

View File

@ -60,55 +60,30 @@ typedef size_t SIZE_T;
typedef SIZE_T *PSIZE_T;
typedef unsigned char BYTE;
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
};
PVOID Pointer;
};
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
#define TRUE 1
#define FALSE 0
#define ERROR_SUCCESS 0
#define ERROR_FILE_NOT_FOUND 2
#define ERROR_PATH_NOT_FOUND 3
#define ERROR_ACCESS_DENIED 5
#define ERROR_INVALID_HANDLE 6
#define ERROR_NOT_ENOUGH_MEMORY 8
#define ERROR_NO_MORE_FILES 18
#define ERROR_READ_FAULT 30
#define ERROR_HANDLE_EOF 38
#define ERROR_NOT_SUPPORTED 50
#define ERROR_INVALID_PARAMETER 87
#define ERROR_CALL_NOT_IMPLEMENTED 120
#define ERROR_BUFFER_OVERFLOW 111
#define ERROR_INSUFFICIENT_BUFFER 122
#define ERROR_NONE_MAPPED 1332
#define ERROR_RESOURCE_DATA_NOT_FOUND 1812
#define ERROR_RESOURCE_TYPE_NOT_FOUND 1813
#define ERROR_RESOURCE_NAME_NOT_FOUND 1814
#define ERROR_RESOURCE_LANG_NOT_FOUND 1815
#define ERROR_MOD_NOT_FOUND 126
#define ERROR_PROC_NOT_FOUND 127
#define ERROR_NEGATIVE_SEEK 131
#define ERROR_BAD_EXE_FORMAT 193
#define ERROR_ALREADY_EXISTS 183
#define ERROR_NOT_OWNER 288
#define STILL_ACTIVE 259
#define TIME_ZONE_ID_UNKNOWN 0
#define TIME_ZONE_ID_STANDARD 1
#define TIME_ZONE_ID_DAYLIGHT 2
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#define INVALID_HANDLE_VALUE ((HANDLE)-1)
typedef int NTSTATUS;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
#define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008)
#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000D)
#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002)
#define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011)
#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BB)
#define STATUS_UNEXPECTED_IO_ERROR ((NTSTATUS)0xC00000E9)
typedef int HRESULT;
#define S_OK ((HRESULT)0x00000000)
#define FILE_FLAG_OVERLAPPED 0x40000000
#define FILE_FLAG_NO_BUFFERING 0x20000000
#define MAX_PATH (260)

View File

@ -1,6 +1,8 @@
#include "common.h"
#include "errors.h"
#include "handles.h"
#include "strutil.h"
#include <algorithm>
#include <sys/random.h>
#include <vector>

View File

@ -1,4 +1,5 @@
#include "common.h"
#include "errors.h"
#include <sys/random.h>
#include <vector>

View File

@ -1,5 +1,6 @@
#include "common.h"
#include "files.h"
#include "errors.h"
#include "processes.h"
#include "handles.h"
#include "resources.h"
@ -454,26 +455,42 @@ namespace kernel32 {
}
void setLastErrorFromErrno() {
switch (errno) {
case 0:
wibo::lastError = ERROR_SUCCESS;
break;
case EACCES:
wibo::lastError = ERROR_ACCESS_DENIED;
break;
case EEXIST:
wibo::lastError = ERROR_ALREADY_EXISTS;
break;
case ENOENT:
wibo::lastError = ERROR_FILE_NOT_FOUND;
break;
case ENOTDIR:
wibo::lastError = ERROR_PATH_NOT_FOUND;
break;
default:
wibo::lastError = ERROR_NOT_SUPPORTED;
break;
wibo::lastError = wibo::winErrorFromErrno(errno);
}
static void setEventSignaledState(HANDLE hEvent, bool signaled) {
if (!hEvent) {
return;
}
auto data = handles::dataFromHandle(hEvent, false);
if (data.type != handles::TYPE_EVENT || data.ptr == nullptr) {
return;
}
EventObject *obj = reinterpret_cast<EventObject *>(data.ptr);
pthread_mutex_lock(&obj->mutex);
obj->signaled = signaled;
if (signaled) {
if (obj->manualReset) {
pthread_cond_broadcast(&obj->cond);
} else {
pthread_cond_signal(&obj->cond);
}
}
pthread_mutex_unlock(&obj->mutex);
}
static void resetOverlappedEvent(OVERLAPPED *ov) {
if (!ov || !ov->hEvent) {
return;
}
setEventSignaledState(ov->hEvent, false);
}
static void signalOverlappedEvent(OVERLAPPED *ov) {
if (!ov || !ov->hEvent) {
return;
}
setEventSignaledState(ov->hEvent, true);
}
uint32_t WIN_FUNC GetLastError() {
@ -1244,10 +1261,10 @@ namespace kernel32 {
(void)dwDesiredAccess;
(void)bInheritHandle;
(void)dwOptions;
FILE *fp = files::fpFromHandle(hSourceHandle);
if (fp == stdin || fp == stdout || fp == stderr) {
auto file = files::fileHandleFromHandle(hSourceHandle);
if (file && (file->fp == stdin || file->fp == stdout || file->fp == stderr)) {
// we never close standard handles so they are fine to duplicate
void *handle = files::allocFpHandle(fp);
void *handle = files::duplicateFileHandle(file, false);
DEBUG_LOG("-> %p\n", handle);
*lpTargetHandle = handle;
return 1;
@ -1261,9 +1278,13 @@ namespace kernel32 {
DEBUG_LOG("CloseHandle(%p)\n", hObject);
auto data = handles::dataFromHandle(hObject, true);
if (data.type == handles::TYPE_FILE) {
FILE *fp = (FILE *) data.ptr;
if (!(fp == stdin || fp == stdout || fp == stderr)) {
fclose(fp);
auto file = reinterpret_cast<files::FileHandle *>(data.ptr);
if (file) {
if (file->closeOnDestroy && file->fp &&
!(file->fp == stdin || file->fp == stdout || file->fp == stderr)) {
fclose(file->fp);
}
delete file;
}
} else if (data.type == handles::TYPE_MAPPED) {
auto *mapping = reinterpret_cast<MappingObject *>(data.ptr);
@ -1272,7 +1293,7 @@ namespace kernel32 {
tryReleaseMapping(mapping);
}
} else if (data.type == handles::TYPE_PROCESS) {
delete (processes::Process*) data.ptr;
delete (processes::Process *)data.ptr;
} else if (data.type == handles::TYPE_TOKEN) {
advapi32::releaseToken(data.ptr);
} else if (data.type == handles::TYPE_MUTEX) {
@ -1820,27 +1841,66 @@ namespace kernel32 {
}
unsigned int WIN_FUNC WriteFile(void *hFile, const void *lpBuffer, unsigned int nNumberOfBytesToWrite, unsigned int *lpNumberOfBytesWritten, void *lpOverlapped) {
DEBUG_LOG("WriteFile(%p, %d)\n", hFile, nNumberOfBytesToWrite);
assert(!lpOverlapped);
wibo::lastError = 0;
DEBUG_LOG("WriteFile(%p, %u)\n", hFile, nNumberOfBytesToWrite);
wibo::lastError = ERROR_SUCCESS;
FILE *fp = files::fpFromHandle(hFile);
size_t written = fwrite(lpBuffer, 1, nNumberOfBytesToWrite, fp);
if (lpNumberOfBytesWritten)
*lpNumberOfBytesWritten = written;
#if 0
printf("writing:\n");
for (unsigned int i = 0; i < nNumberOfBytesToWrite; i++) {
printf("%c", ((const char*)lpBuffer)[i]);
auto file = files::fileHandleFromHandle(hFile);
if (!file || !file->fp) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
printf("\n");
#endif
if (written == 0)
wibo::lastError = 29; // ERROR_WRITE_FAULT
bool handleOverlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0;
auto *overlapped = reinterpret_cast<OVERLAPPED *>(lpOverlapped);
bool usingOverlapped = overlapped != nullptr;
if (!usingOverlapped && lpNumberOfBytesWritten == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
return (written == nNumberOfBytesToWrite);
std::optional<uint64_t> offset;
bool updateFilePointer = true;
if (usingOverlapped) {
offset = (static_cast<uint64_t>(overlapped->Offset) | (static_cast<uint64_t>(overlapped->OffsetHigh) << 32));
overlapped->Internal = STATUS_PENDING;
overlapped->InternalHigh = 0;
updateFilePointer = !handleOverlapped;
resetOverlappedEvent(overlapped);
}
auto io = files::write(file, lpBuffer, nNumberOfBytesToWrite, offset, updateFilePointer);
DWORD completionStatus = STATUS_SUCCESS;
if (io.unixError != 0) {
completionStatus = wibo::winErrorFromErrno(io.unixError);
wibo::lastError = completionStatus;
if (lpNumberOfBytesWritten) {
*lpNumberOfBytesWritten = static_cast<DWORD>(io.bytesTransferred);
}
if (usingOverlapped) {
overlapped->Internal = completionStatus;
overlapped->InternalHigh = io.bytesTransferred;
signalOverlappedEvent(overlapped);
}
return FALSE;
}
if (lpNumberOfBytesWritten && (!handleOverlapped || !usingOverlapped)) {
*lpNumberOfBytesWritten = static_cast<DWORD>(io.bytesTransferred);
}
if (usingOverlapped) {
overlapped->Internal = completionStatus;
overlapped->InternalHigh = io.bytesTransferred;
if (!handleOverlapped) {
uint64_t baseOffset = offset.value_or(0);
uint64_t newOffset = baseOffset + io.bytesTransferred;
overlapped->Offset = static_cast<DWORD>(newOffset & 0xFFFFFFFFu);
overlapped->OffsetHigh = static_cast<DWORD>(newOffset >> 32);
}
signalOverlappedEvent(overlapped);
}
return (io.bytesTransferred == nNumberOfBytesToWrite);
}
BOOL WIN_FUNC FlushFileBuffers(HANDLE hFile) {
@ -1850,12 +1910,17 @@ namespace kernel32 {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
FILE *fp = reinterpret_cast<FILE *>(data.ptr);
auto file = reinterpret_cast<files::FileHandle *>(data.ptr);
if (!file || !file->fp) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
FILE *fp = file->fp;
if (fflush(fp) != 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
return FALSE;
}
int fd = fileno(fp);
int fd = file->fd;
if (fd >= 0 && fsync(fd) != 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
return FALSE;
@ -1865,14 +1930,70 @@ namespace kernel32 {
}
unsigned int WIN_FUNC ReadFile(void *hFile, void *lpBuffer, unsigned int nNumberOfBytesToRead, unsigned int *lpNumberOfBytesRead, void *lpOverlapped) {
DEBUG_LOG("ReadFile %p %d\n", hFile, nNumberOfBytesToRead);
assert(!lpOverlapped);
wibo::lastError = 0;
DEBUG_LOG("ReadFile(%p, %u)\n", hFile, nNumberOfBytesToRead);
wibo::lastError = ERROR_SUCCESS;
FILE *fp = files::fpFromHandle(hFile);
size_t read = fread(lpBuffer, 1, nNumberOfBytesToRead, fp);
*lpNumberOfBytesRead = read;
return 1;
auto file = files::fileHandleFromHandle(hFile);
if (!file || !file->fp) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
bool handleOverlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0;
auto *overlapped = reinterpret_cast<OVERLAPPED *>(lpOverlapped);
bool usingOverlapped = overlapped != nullptr;
if (!usingOverlapped && lpNumberOfBytesRead == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
std::optional<uint64_t> offset;
bool updateFilePointer = true;
if (usingOverlapped) {
offset = (static_cast<uint64_t>(overlapped->Offset) | (static_cast<uint64_t>(overlapped->OffsetHigh) << 32));
overlapped->Internal = STATUS_PENDING;
overlapped->InternalHigh = 0;
updateFilePointer = !handleOverlapped;
resetOverlappedEvent(overlapped);
}
auto io = files::read(file, lpBuffer, nNumberOfBytesToRead, offset, updateFilePointer);
DWORD completionStatus = STATUS_SUCCESS;
if (io.unixError != 0) {
completionStatus = wibo::winErrorFromErrno(io.unixError);
wibo::lastError = completionStatus;
if (lpNumberOfBytesRead) {
*lpNumberOfBytesRead = static_cast<DWORD>(io.bytesTransferred);
}
if (usingOverlapped) {
overlapped->Internal = completionStatus;
overlapped->InternalHigh = io.bytesTransferred;
signalOverlappedEvent(overlapped);
}
return FALSE;
}
if (io.reachedEnd && io.bytesTransferred == 0 && handleOverlapped) {
completionStatus = ERROR_HANDLE_EOF;
}
if (lpNumberOfBytesRead && (!handleOverlapped || !usingOverlapped)) {
*lpNumberOfBytesRead = static_cast<DWORD>(io.bytesTransferred);
}
if (usingOverlapped) {
overlapped->Internal = completionStatus;
overlapped->InternalHigh = io.bytesTransferred;
if (!handleOverlapped) {
uint64_t baseOffset = offset.value_or(0);
uint64_t newOffset = baseOffset + io.bytesTransferred;
overlapped->Offset = static_cast<DWORD>(newOffset & 0xFFFFFFFFu);
overlapped->OffsetHigh = static_cast<DWORD>(newOffset >> 32);
}
signalOverlappedEvent(overlapped);
}
return TRUE;
}
enum {
@ -1961,7 +2082,7 @@ namespace kernel32 {
}
if (fp) {
void *handle = files::allocFpHandle(fp);
void *handle = files::allocFpHandle(fp, dwDesiredAccess, dwShareMode, dwFlagsAndAttributes, true);
DEBUG_LOG("-> %p\n", handle);
return handle;
} else {
@ -4665,8 +4786,42 @@ namespace kernel32 {
}
BOOL WIN_FUNC GetOverlappedResult(void *hFile, void *lpOverlapped, int *lpNumberOfBytesTransferred, BOOL bWait) {
// DEBUG_LOG("GetOverlappedResult(%p, %p, %p, %u)\n", hFile, lpOverlapped, lpNumberOfBytesTransferred, bWait);
return 1;
(void)hFile;
OVERLAPPED *overlapped = reinterpret_cast<OVERLAPPED *>(lpOverlapped);
if (!overlapped) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
if (bWait && overlapped->Internal == STATUS_PENDING) {
if (overlapped->hEvent) {
WaitForSingleObject(overlapped->hEvent, 0xFFFFFFFF);
}
}
DWORD status = static_cast<DWORD>(overlapped->Internal);
if (status == STATUS_PENDING) {
wibo::lastError = ERROR_IO_INCOMPLETE;
if (lpNumberOfBytesTransferred) {
*lpNumberOfBytesTransferred = static_cast<int>(overlapped->InternalHigh);
}
return FALSE;
}
if (lpNumberOfBytesTransferred) {
*lpNumberOfBytesTransferred = static_cast<int>(overlapped->InternalHigh);
}
if (status == STATUS_SUCCESS) {
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
if (status == STATUS_END_OF_FILE || status == ERROR_HANDLE_EOF) {
wibo::lastError = ERROR_HANDLE_EOF;
return FALSE;
}
wibo::lastError = status;
return FALSE;
}
}

View File

@ -1,8 +1,11 @@
#include "common.h"
#include "errors.h"
#include "files.h"
#include <sys/mman.h>
#include <optional>
#define PIO_APC_ROUTINE void *
typedef struct _IO_STATUS_BLOCK {
@ -13,6 +16,11 @@ typedef struct _IO_STATUS_BLOCK {
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
namespace kernel32 {
BOOL WIN_FUNC SetEvent(HANDLE hEvent);
BOOL WIN_FUNC ResetEvent(HANDLE hEvent);
} // namespace kernel32
namespace ntdll {
NTSTATUS WIN_FUNC NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
@ -20,34 +28,55 @@ NTSTATUS WIN_FUNC NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE Ap
PULONG Key) {
DEBUG_LOG("NtReadFile(%p, %p, %p, %p, %p, %p, %u, %p, %p) ", FileHandle, Event, ApcRoutine, ApcContext,
IoStatusBlock, Buffer, Length, ByteOffset, Key);
assert(Event == nullptr);
assert(ApcRoutine == nullptr);
assert(ApcContext == nullptr);
assert(ByteOffset == nullptr);
assert(Key == nullptr);
(void)ApcRoutine;
(void)ApcContext;
(void)Key;
wibo::lastError = 0;
FILE *fp = files::fpFromHandle(FileHandle);
if (!fp) {
if (!IoStatusBlock) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return STATUS_INVALID_PARAMETER;
}
auto file = files::fileHandleFromHandle(FileHandle);
if (!file || !file->fp) {
wibo::lastError = ERROR_INVALID_HANDLE;
IoStatusBlock->Status = STATUS_INVALID_HANDLE;
IoStatusBlock->Information = 0;
return STATUS_INVALID_HANDLE;
}
size_t read = fread(Buffer, 1, Length, fp);
bool handleOverlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0;
std::optional<uint64_t> offset;
bool updateFilePointer = !handleOverlapped;
if (ByteOffset) {
offset = static_cast<uint64_t>(*ByteOffset);
} else if (handleOverlapped) {
updateFilePointer = false;
}
if (Event) {
kernel32::ResetEvent(Event);
}
auto io = files::read(file, Buffer, Length, offset, updateFilePointer);
DWORD winError = ERROR_SUCCESS;
NTSTATUS status = STATUS_SUCCESS;
if (read < Length) {
if (feof(fp)) {
wibo::lastError = ERROR_HANDLE_EOF;
status = STATUS_END_OF_FILE;
} else {
wibo::lastError = ERROR_READ_FAULT; // ?
status = STATUS_UNEXPECTED_IO_ERROR;
}
if (io.unixError != 0) {
winError = wibo::winErrorFromErrno(io.unixError);
status = wibo::statusFromWinError(winError);
} else if (io.reachedEnd && io.bytesTransferred == 0) {
winError = ERROR_HANDLE_EOF;
status = STATUS_END_OF_FILE;
}
if (IoStatusBlock) {
IoStatusBlock->Status = status;
IoStatusBlock->Information = read;
IoStatusBlock->Status = status;
IoStatusBlock->Information = static_cast<ULONG_PTR>(io.bytesTransferred);
wibo::lastError = winError;
if (Event) {
kernel32::SetEvent(Event);
}
DEBUG_LOG("-> 0x%x\n", status);
return status;
}

View File

@ -1,4 +1,5 @@
#include "common.h"
#include "errors.h"
#include "handles.h"
namespace psapi {

View File

@ -1,4 +1,5 @@
#include "common.h"
#include "errors.h"
#include "files.h"
#include "resources.h"
#include "strutil.h"

53
errors.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "errors.h"
#include <cerrno>
namespace wibo {
DWORD winErrorFromErrno(int err) {
switch (err) {
case 0:
return ERROR_SUCCESS;
case EACCES:
return ERROR_ACCESS_DENIED;
case EEXIST:
return ERROR_ALREADY_EXISTS;
case ENOENT:
return ERROR_FILE_NOT_FOUND;
case ENOTDIR:
return ERROR_PATH_NOT_FOUND;
case ENOMEM:
return ERROR_NOT_ENOUGH_MEMORY;
case EINVAL:
return ERROR_INVALID_PARAMETER;
case EINTR:
return ERROR_OPERATION_ABORTED;
case EIO:
return ERROR_READ_FAULT;
case EPIPE:
return ERROR_BROKEN_PIPE;
case ESPIPE:
return ERROR_INVALID_PARAMETER;
default:
DEBUG_LOG("Unhandled errno %d -> ERROR_NOT_SUPPORTED\n", err);
return ERROR_NOT_SUPPORTED;
}
}
NTSTATUS statusFromWinError(DWORD error) {
switch (error) {
case ERROR_SUCCESS:
return STATUS_SUCCESS;
case ERROR_INVALID_HANDLE:
return STATUS_INVALID_HANDLE;
case ERROR_INVALID_PARAMETER:
return STATUS_INVALID_PARAMETER;
case ERROR_HANDLE_EOF:
return STATUS_END_OF_FILE;
default:
return STATUS_UNEXPECTED_IO_ERROR;
}
}
} // namespace wibo

54
errors.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include "common.h"
#define ERROR_SUCCESS 0
#define ERROR_FILE_NOT_FOUND 2
#define ERROR_PATH_NOT_FOUND 3
#define ERROR_ACCESS_DENIED 5
#define ERROR_INVALID_HANDLE 6
#define ERROR_NOT_ENOUGH_MEMORY 8
#define ERROR_NO_MORE_FILES 18
#define ERROR_READ_FAULT 30
#define ERROR_HANDLE_EOF 38
#define ERROR_BROKEN_PIPE 109
#define ERROR_NOT_SUPPORTED 50
#define ERROR_INVALID_PARAMETER 87
#define ERROR_CALL_NOT_IMPLEMENTED 120
#define ERROR_BUFFER_OVERFLOW 111
#define ERROR_INSUFFICIENT_BUFFER 122
#define ERROR_IO_INCOMPLETE 996
#define ERROR_IO_PENDING 997
#define ERROR_OPERATION_ABORTED 995
#define ERROR_NONE_MAPPED 1332
#define ERROR_RESOURCE_DATA_NOT_FOUND 1812
#define ERROR_RESOURCE_TYPE_NOT_FOUND 1813
#define ERROR_RESOURCE_NAME_NOT_FOUND 1814
#define ERROR_RESOURCE_LANG_NOT_FOUND 1815
#define ERROR_MOD_NOT_FOUND 126
#define ERROR_PROC_NOT_FOUND 127
#define ERROR_NEGATIVE_SEEK 131
#define ERROR_BAD_EXE_FORMAT 193
#define ERROR_ALREADY_EXISTS 183
#define ERROR_NOT_OWNER 288
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#define INVALID_HANDLE_VALUE ((HANDLE)-1)
typedef int NTSTATUS;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
#define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008)
#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000D)
#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002)
#define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011)
#define STATUS_PENDING ((NTSTATUS)0x00000103)
#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BB)
#define STATUS_UNEXPECTED_IO_ERROR ((NTSTATUS)0xC00000E9)
typedef int HRESULT;
#define S_OK ((HRESULT)0x00000000)
namespace wibo {
DWORD winErrorFromErrno(int err);
NTSTATUS statusFromWinError(DWORD error);
}

178
files.cpp
View File

@ -3,6 +3,7 @@
#include "handles.h"
#include "strutil.h"
#include <algorithm>
#include <cerrno>
#include <map>
#include <optional>
#include <strings.h>
@ -120,20 +121,183 @@ namespace files {
return str;
}
FileHandle *fileHandleFromHandle(void *handle) {
handles::Data data = handles::dataFromHandle(handle, false);
if (data.type != handles::TYPE_FILE) {
return nullptr;
}
return reinterpret_cast<FileHandle *>(data.ptr);
}
FILE *fpFromHandle(void *handle, bool pop) {
handles::Data data = handles::dataFromHandle(handle, pop);
if (data.type == handles::TYPE_FILE) {
return (FILE*)data.ptr;
return reinterpret_cast<FileHandle *>(data.ptr)->fp;
} else if (data.type == handles::TYPE_UNUSED && pop) {
return 0;
return nullptr;
} else {
printf("Invalid file handle %p\n", handle);
assert(0);
}
}
void *allocFpHandle(FILE *fp) {
return handles::allocDataHandle(handles::Data{handles::TYPE_FILE, fp, 0});
void *allocFpHandle(FILE *fp, unsigned int desiredAccess, unsigned int shareMode, unsigned int flags, bool closeOnDestroy) {
auto *handle = new FileHandle();
handle->fp = fp;
handle->fd = (fp ? fileno(fp) : -1);
handle->desiredAccess = desiredAccess;
handle->shareMode = shareMode;
handle->flags = flags;
handle->closeOnDestroy = closeOnDestroy;
return handles::allocDataHandle({handles::TYPE_FILE, handle, 0});
}
void *duplicateFileHandle(FileHandle *source, bool closeOnDestroy) {
if (!source) {
return nullptr;
}
auto *clone = new FileHandle();
clone->fp = source->fp;
clone->fd = source->fd;
clone->desiredAccess = source->desiredAccess;
clone->shareMode = source->shareMode;
clone->flags = source->flags;
clone->closeOnDestroy = closeOnDestroy;
return handles::allocDataHandle({handles::TYPE_FILE, clone, 0});
}
IOResult read(FileHandle *handle, void *buffer, size_t bytesToRead, const std::optional<uint64_t> &offset, bool updateFilePointer) {
IOResult result{};
if (!handle || !handle->fp) {
result.unixError = EBADF;
return result;
}
if (bytesToRead == 0) {
return result;
}
bool useOffset = offset.has_value();
if (useOffset && !updateFilePointer && handle->fd >= 0) {
size_t total = 0;
size_t remaining = bytesToRead;
off_t pos = static_cast<off_t>(*offset);
while (remaining > 0) {
ssize_t rc = pread(handle->fd, static_cast<uint8_t *>(buffer) + total, remaining, pos);
if (rc == -1) {
if (errno == EINTR) {
continue;
}
result.bytesTransferred = total;
result.unixError = errno ? errno : EIO;
return result;
}
if (rc == 0) {
result.bytesTransferred = total;
result.reachedEnd = true;
return result;
}
total += static_cast<size_t>(rc);
remaining -= static_cast<size_t>(rc);
pos += rc;
}
result.bytesTransferred = total;
return result;
}
off_t originalPos = -1;
std::unique_lock<std::mutex> lock(handle->mutex);
if (useOffset) {
originalPos = ftello(handle->fp);
if (!updateFilePointer && originalPos == -1) {
result.unixError = errno ? errno : ESPIPE;
return result;
}
if (fseeko(handle->fp, static_cast<off_t>(*offset), SEEK_SET) != 0) {
result.unixError = errno ? errno : EINVAL;
return result;
}
}
size_t readCount = fread(buffer, 1, bytesToRead, handle->fp);
result.bytesTransferred = readCount;
if (readCount < bytesToRead) {
if (feof(handle->fp)) {
result.reachedEnd = true;
clearerr(handle->fp);
} else if (ferror(handle->fp)) {
result.unixError = errno ? errno : EIO;
clearerr(handle->fp);
}
}
if (useOffset && !updateFilePointer) {
if (originalPos != -1) {
fseeko(handle->fp, originalPos, SEEK_SET);
}
}
return result;
}
IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional<uint64_t> &offset, bool updateFilePointer) {
IOResult result{};
if (!handle || !handle->fp) {
result.unixError = EBADF;
return result;
}
if (bytesToWrite == 0) {
return result;
}
bool useOffset = offset.has_value();
if (useOffset && !updateFilePointer && handle->fd >= 0) {
size_t total = 0;
size_t remaining = bytesToWrite;
off_t pos = static_cast<off_t>(*offset);
while (remaining > 0) {
ssize_t rc = pwrite(handle->fd, static_cast<const uint8_t *>(buffer) + total, remaining, pos);
if (rc == -1) {
if (errno == EINTR) {
continue;
}
result.bytesTransferred = total;
result.unixError = errno ? errno : EIO;
return result;
}
total += static_cast<size_t>(rc);
remaining -= static_cast<size_t>(rc);
pos += rc;
}
result.bytesTransferred = total;
return result;
}
off_t originalPos = -1;
std::unique_lock<std::mutex> lock(handle->mutex);
if (useOffset) {
originalPos = ftello(handle->fp);
if (!updateFilePointer && originalPos == -1) {
result.unixError = errno ? errno : ESPIPE;
return result;
}
if (fseeko(handle->fp, static_cast<off_t>(*offset), SEEK_SET) != 0) {
result.unixError = errno ? errno : EINVAL;
return result;
}
}
size_t writeCount = fwrite(buffer, 1, bytesToWrite, handle->fp);
result.bytesTransferred = writeCount;
if (writeCount < bytesToWrite) {
result.unixError = errno ? errno : EIO;
clearerr(handle->fp);
}
if (useOffset && !updateFilePointer) {
if (originalPos != -1) {
fseeko(handle->fp, originalPos, SEEK_SET);
}
}
return result;
}
void *getStdHandle(uint32_t nStdHandle) {
@ -167,9 +331,9 @@ namespace files {
}
void init() {
stdinHandle = allocFpHandle(stdin);
stdoutHandle = allocFpHandle(stdout);
stderrHandle = allocFpHandle(stderr);
stdinHandle = allocFpHandle(stdin, 0, 0, 0, false);
stdoutHandle = allocFpHandle(stdout, 0, 0, 0, false);
stderrHandle = allocFpHandle(stderr, 0, 0, 0, false);
}
std::optional<std::filesystem::path> findCaseInsensitiveFile(const std::filesystem::path &directory,

24
files.h
View File

@ -1,14 +1,36 @@
#pragma once
#include <cstdio>
#include <filesystem>
#include <mutex>
#include <optional>
#include <string>
namespace files {
struct FileHandle {
FILE *fp = nullptr;
int fd = -1;
unsigned int desiredAccess = 0;
unsigned int shareMode = 0;
unsigned int flags = 0;
bool closeOnDestroy = true;
std::mutex mutex;
};
struct IOResult {
size_t bytesTransferred = 0;
int unixError = 0;
bool reachedEnd = false;
};
std::filesystem::path pathFromWindows(const char *inStr);
std::string pathToWindows(const std::filesystem::path &path);
void *allocFpHandle(FILE *fp);
void *allocFpHandle(FILE *fp, unsigned int desiredAccess = 0, unsigned int shareMode = 0, unsigned int flags = 0, bool closeOnDestroy = true);
void *duplicateFileHandle(FileHandle *handle, bool closeOnDestroy);
FILE *fpFromHandle(void *handle, bool pop = false);
FileHandle *fileHandleFromHandle(void *handle);
IOResult read(FileHandle *handle, void *buffer, size_t bytesToRead, const std::optional<uint64_t> &offset, bool updateFilePointer);
IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional<uint64_t> &offset, bool updateFilePointer);
void *getStdHandle(uint32_t nStdHandle);
unsigned int setStdHandle(uint32_t nStdHandle, void *hHandle);
void init();

View File

@ -1,4 +1,5 @@
#include "common.h"
#include "errors.h"
#include "files.h"
#include "strutil.h"

View File

@ -1,4 +1,5 @@
#include "resources.h"
#include "errors.h"
#include "common.h"
namespace {

165
test/test_overlapped_io.c Normal file
View File

@ -0,0 +1,165 @@
#include "test_assert.h"
#include <windows.h>
#ifndef STATUS_PENDING
#define STATUS_PENDING ((DWORD)0x00000103)
#endif
static const char *kFilename = "overlapped_test.tmp";
static void write_fixture_file(void) {
HANDLE file = CreateFileA(kFilename, GENERIC_WRITE | GENERIC_READ, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
const char contents[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
DWORD written = 0;
TEST_CHECK(WriteFile(file, contents, (DWORD)(sizeof(contents) - 1), &written, NULL));
TEST_CHECK_EQ(sizeof(contents) - 1, written);
TEST_CHECK(CloseHandle(file));
}
static void test_synchronous_overlapped_read(void) {
HANDLE file = CreateFileA(kFilename, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
OVERLAPPED ov = {0};
ov.Offset = 5;
char buffer[16] = {0};
DWORD bytesRead = 0;
TEST_CHECK(ReadFile(file, buffer, 7, &bytesRead, &ov));
TEST_CHECK_EQ(7, bytesRead);
buffer[7] = '\0';
TEST_CHECK_STR_EQ("56789AB", buffer);
DWORD pos = SetFilePointer(file, 0, NULL, FILE_CURRENT);
TEST_CHECK_EQ(12, (int)pos);
unsigned long long trackedOffset = ((unsigned long long)ov.OffsetHigh << 32) | ov.Offset;
// Wine leaves OVERLAPPED.Offset unchanged for synchronous handles, even though the Win32 docs state the runtime should advance both the
// file pointer and the OVERLAPPED offsets. We intentionally skip asserting the
// offset here so the fixture passes under both wibo and wine.
// TEST_CHECK_U64_EQ(12ULL, trackedOffset);
(void)trackedOffset;
TEST_CHECK(CloseHandle(file));
}
static void test_overlapped_read_with_event(void) {
HANDLE file = CreateFileA(kFilename, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
OVERLAPPED ov = {0};
ov.Offset = 10;
ov.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(ov.hEvent != NULL);
char buffer[16] = {0};
BOOL issued = ReadFile(file, buffer, 6, NULL, &ov);
if (!issued) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
TEST_CHECK(WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0);
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, FALSE));
TEST_CHECK_EQ(6U, transferred);
buffer[6] = '\0';
TEST_CHECK_STR_EQ("ABCDEF", buffer);
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
}
static void test_overlapped_eof(void) {
HANDLE file = CreateFileA(kFilename, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
OVERLAPPED ov = {0};
ov.Offset = 80; /* beyond end */
ov.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(ov.hEvent != NULL);
char buffer[8] = {0};
BOOL issued = ReadFile(file, buffer, sizeof(buffer), NULL, &ov);
if (!issued) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
TEST_CHECK(WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0);
DWORD transferred = 1234;
TEST_CHECK(!GetOverlappedResult(file, &ov, &transferred, FALSE));
TEST_CHECK_EQ(ERROR_HANDLE_EOF, GetLastError());
TEST_CHECK_EQ(0U, transferred);
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
}
static void test_getoverlappedresult_pending(void) {
OVERLAPPED ov = {0};
ov.Internal = STATUS_PENDING;
ov.InternalHigh = 42;
DWORD transferred = 0;
TEST_CHECK(!GetOverlappedResult(NULL, &ov, &transferred, FALSE));
TEST_CHECK_EQ(ERROR_IO_INCOMPLETE, GetLastError());
// Wine leaves the caller-supplied transfer count untouched for the
// pending case, so we avoid asserting on the value here.
// TEST_CHECK_EQ(42U, transferred);
}
static void test_overlapped_write(void) {
HANDLE file = CreateFileA(kFilename, GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
OVERLAPPED ov = {0};
ov.Offset = 2;
ov.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
TEST_CHECK(ov.hEvent != NULL);
const char patch[] = "zz";
BOOL issued = WriteFile(file, patch, (DWORD)(sizeof(patch) - 1), NULL, &ov);
if (!issued) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
TEST_CHECK(WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0);
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, FALSE));
TEST_CHECK_EQ((DWORD)(sizeof(patch) - 1), transferred);
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
HANDLE verify = CreateFileA(kFilename, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK(verify != INVALID_HANDLE_VALUE);
TEST_CHECK(SetFilePointer(verify, 2, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER);
char buffer[3] = {0};
DWORD bytesRead = 0;
TEST_CHECK(ReadFile(verify, buffer, sizeof(patch) - 1, &bytesRead, NULL));
TEST_CHECK_EQ((DWORD)(sizeof(patch) - 1), bytesRead);
TEST_CHECK(buffer[0] == 'z' && buffer[1] == 'z');
TEST_CHECK(CloseHandle(verify));
}
int main(void) {
DeleteFileA(kFilename);
write_fixture_file();
test_synchronous_overlapped_read();
test_overlapped_read_with_event();
test_overlapped_eof();
test_getoverlappedresult_pending();
test_overlapped_write();
TEST_CHECK(DeleteFileA(kFilename));
return 0;
}