From 62d8daccacb82f6c1992ef45779043a644b92d08 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 29 Sep 2025 23:32:17 -0600 Subject: [PATCH] Overlapped I/O support (fixes ProDG compilers) --- CMakeLists.txt | 21 ++- common.h | 55 +++----- dll/advapi32.cpp | 2 + dll/bcrypt.cpp | 1 + dll/kernel32.cpp | 265 ++++++++++++++++++++++++++++++-------- dll/ntdll.cpp | 69 +++++++--- dll/psapi.cpp | 1 + dll/version.cpp | 1 + errors.cpp | 53 ++++++++ errors.h | 54 ++++++++ files.cpp | 178 ++++++++++++++++++++++++- files.h | 24 +++- module_registry.cpp | 1 + resources.cpp | 1 + test/test_overlapped_io.c | 165 ++++++++++++++++++++++++ 15 files changed, 767 insertions(+), 124 deletions(-) create mode 100644 errors.cpp create mode 100644 errors.h create mode 100644 test/test_overlapped_io.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7795a2a..c03680f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 $ ${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() diff --git a/common.h b/common.h index 4361b60..2b54aac 100644 --- a/common.h +++ b/common.h @@ -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) diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index 03039a2..ebf1d0f 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -1,6 +1,8 @@ #include "common.h" +#include "errors.h" #include "handles.h" #include "strutil.h" + #include #include #include diff --git a/dll/bcrypt.cpp b/dll/bcrypt.cpp index 054e30b..e53d6b2 100644 --- a/dll/bcrypt.cpp +++ b/dll/bcrypt.cpp @@ -1,4 +1,5 @@ #include "common.h" +#include "errors.h" #include #include diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 501ccbc..38aab28 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -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(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(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(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(lpOverlapped); + bool usingOverlapped = overlapped != nullptr; + if (!usingOverlapped && lpNumberOfBytesWritten == nullptr) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } - return (written == nNumberOfBytesToWrite); + std::optional offset; + bool updateFilePointer = true; + if (usingOverlapped) { + offset = (static_cast(overlapped->Offset) | (static_cast(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(io.bytesTransferred); + } + if (usingOverlapped) { + overlapped->Internal = completionStatus; + overlapped->InternalHigh = io.bytesTransferred; + signalOverlappedEvent(overlapped); + } + return FALSE; + } + + if (lpNumberOfBytesWritten && (!handleOverlapped || !usingOverlapped)) { + *lpNumberOfBytesWritten = static_cast(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(newOffset & 0xFFFFFFFFu); + overlapped->OffsetHigh = static_cast(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(data.ptr); + auto file = reinterpret_cast(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(lpOverlapped); + bool usingOverlapped = overlapped != nullptr; + if (!usingOverlapped && lpNumberOfBytesRead == nullptr) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + + std::optional offset; + bool updateFilePointer = true; + if (usingOverlapped) { + offset = (static_cast(overlapped->Offset) | (static_cast(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(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(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(newOffset & 0xFFFFFFFFu); + overlapped->OffsetHigh = static_cast(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(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(overlapped->Internal); + if (status == STATUS_PENDING) { + wibo::lastError = ERROR_IO_INCOMPLETE; + if (lpNumberOfBytesTransferred) { + *lpNumberOfBytesTransferred = static_cast(overlapped->InternalHigh); + } + return FALSE; + } + + if (lpNumberOfBytesTransferred) { + *lpNumberOfBytesTransferred = static_cast(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; } } diff --git a/dll/ntdll.cpp b/dll/ntdll.cpp index d992647..180df67 100644 --- a/dll/ntdll.cpp +++ b/dll/ntdll.cpp @@ -1,8 +1,11 @@ #include "common.h" +#include "errors.h" #include "files.h" #include +#include + #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 offset; + bool updateFilePointer = !handleOverlapped; + if (ByteOffset) { + offset = static_cast(*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(io.bytesTransferred); + wibo::lastError = winError; + + if (Event) { + kernel32::SetEvent(Event); } + DEBUG_LOG("-> 0x%x\n", status); return status; } diff --git a/dll/psapi.cpp b/dll/psapi.cpp index 65dd6f7..63dd0cc 100644 --- a/dll/psapi.cpp +++ b/dll/psapi.cpp @@ -1,4 +1,5 @@ #include "common.h" +#include "errors.h" #include "handles.h" namespace psapi { diff --git a/dll/version.cpp b/dll/version.cpp index 2bbefcf..9174201 100644 --- a/dll/version.cpp +++ b/dll/version.cpp @@ -1,4 +1,5 @@ #include "common.h" +#include "errors.h" #include "files.h" #include "resources.h" #include "strutil.h" diff --git a/errors.cpp b/errors.cpp new file mode 100644 index 0000000..a8303d3 --- /dev/null +++ b/errors.cpp @@ -0,0 +1,53 @@ + +#include "errors.h" + +#include + +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 diff --git a/errors.h b/errors.h new file mode 100644 index 0000000..5e24563 --- /dev/null +++ b/errors.h @@ -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); +} diff --git a/files.cpp b/files.cpp index c91649c..7e337ce 100644 --- a/files.cpp +++ b/files.cpp @@ -3,6 +3,7 @@ #include "handles.h" #include "strutil.h" #include +#include #include #include #include @@ -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(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(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 &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(*offset); + while (remaining > 0) { + ssize_t rc = pread(handle->fd, static_cast(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(rc); + remaining -= static_cast(rc); + pos += rc; + } + result.bytesTransferred = total; + return result; + } + + off_t originalPos = -1; + std::unique_lock 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(*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 &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(*offset); + while (remaining > 0) { + ssize_t rc = pwrite(handle->fd, static_cast(buffer) + total, remaining, pos); + if (rc == -1) { + if (errno == EINTR) { + continue; + } + result.bytesTransferred = total; + result.unixError = errno ? errno : EIO; + return result; + } + total += static_cast(rc); + remaining -= static_cast(rc); + pos += rc; + } + result.bytesTransferred = total; + return result; + } + + off_t originalPos = -1; + std::unique_lock 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(*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 findCaseInsensitiveFile(const std::filesystem::path &directory, diff --git a/files.h b/files.h index 329a4d4..ce87563 100644 --- a/files.h +++ b/files.h @@ -1,14 +1,36 @@ #pragma once +#include #include +#include #include #include 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 &offset, bool updateFilePointer); + IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional &offset, bool updateFilePointer); void *getStdHandle(uint32_t nStdHandle); unsigned int setStdHandle(uint32_t nStdHandle, void *hHandle); void init(); diff --git a/module_registry.cpp b/module_registry.cpp index 1cde806..24bc1a9 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -1,4 +1,5 @@ #include "common.h" +#include "errors.h" #include "files.h" #include "strutil.h" diff --git a/resources.cpp b/resources.cpp index 19bd093..6bd367f 100644 --- a/resources.cpp +++ b/resources.cpp @@ -1,4 +1,5 @@ #include "resources.h" +#include "errors.h" #include "common.h" namespace { diff --git a/test/test_overlapped_io.c b/test/test_overlapped_io.c new file mode 100644 index 0000000..5bf5f9d --- /dev/null +++ b/test/test_overlapped_io.c @@ -0,0 +1,165 @@ +#include "test_assert.h" +#include + +#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; +}