Fix GetOverlappedResult without event & improve overlapped handling

This commit is contained in:
2025-10-23 01:07:26 -06:00
parent 4d5caf91e1
commit 5597da607a
8 changed files with 459 additions and 281 deletions

View File

@@ -729,9 +729,7 @@ BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr
detail::resetOverlappedEvent(lpOverlapped);
if (file->overlapped) {
if (nNumberOfBytesToWrite == 0) {
lpOverlapped->Internal = STATUS_SUCCESS;
lpOverlapped->InternalHigh = 0;
detail::signalOverlappedEvent(lpOverlapped);
detail::signalOverlappedEvent(file.get(), lpOverlapped, STATUS_SUCCESS, 0);
if (lpNumberOfBytesWritten) {
*lpNumberOfBytesWritten = 0;
}
@@ -749,7 +747,7 @@ BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr
}
auto io = files::write(file.get(), lpBuffer, nNumberOfBytesToWrite, offset, updateFilePointer);
DWORD completionStatus = STATUS_SUCCESS;
NTSTATUS completionStatus = STATUS_SUCCESS;
if (io.unixError != 0) {
completionStatus = wibo::statusFromErrno(io.unixError);
wibo::lastError = wibo::winErrorFromErrno(io.unixError);
@@ -761,11 +759,7 @@ BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr
*lpNumberOfBytesWritten = static_cast<DWORD>(io.bytesTransferred);
}
if (lpOverlapped != nullptr) {
lpOverlapped->Internal = completionStatus;
lpOverlapped->InternalHigh = io.bytesTransferred;
detail::signalOverlappedEvent(lpOverlapped);
}
detail::signalOverlappedEvent(file.get(), lpOverlapped, completionStatus, io.bytesTransferred);
return io.unixError == 0;
}
@@ -825,9 +819,7 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
detail::resetOverlappedEvent(lpOverlapped);
if (file->overlapped) {
if (nNumberOfBytesToRead == 0) {
lpOverlapped->Internal = STATUS_SUCCESS;
lpOverlapped->InternalHigh = 0;
detail::signalOverlappedEvent(lpOverlapped);
detail::signalOverlappedEvent(file.get(), lpOverlapped, STATUS_SUCCESS, 0);
if (lpNumberOfBytesRead) {
*lpNumberOfBytesRead = 0;
}
@@ -845,7 +837,7 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
}
auto io = files::read(file.get(), lpBuffer, nNumberOfBytesToRead, offset, updateFilePointer);
DWORD completionStatus = STATUS_SUCCESS;
NTSTATUS completionStatus = STATUS_SUCCESS;
if (io.unixError != 0) {
completionStatus = wibo::statusFromErrno(io.unixError);
wibo::lastError = wibo::winErrorFromErrno(io.unixError);
@@ -853,11 +845,7 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
if (file->isPipe) {
completionStatus = STATUS_PIPE_BROKEN;
wibo::lastError = ERROR_BROKEN_PIPE;
if (lpOverlapped != nullptr) {
lpOverlapped->Internal = completionStatus;
lpOverlapped->InternalHigh = 0;
detail::signalOverlappedEvent(lpOverlapped);
}
detail::signalOverlappedEvent(file.get(), lpOverlapped, completionStatus, 0);
DEBUG_LOG("-> ERROR_BROKEN_PIPE\n");
return FALSE;
}
@@ -868,11 +856,7 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
*lpNumberOfBytesRead = static_cast<DWORD>(io.bytesTransferred);
}
if (lpOverlapped != nullptr) {
lpOverlapped->Internal = completionStatus;
lpOverlapped->InternalHigh = io.bytesTransferred;
detail::signalOverlappedEvent(lpOverlapped);
}
detail::signalOverlappedEvent(file.get(), lpOverlapped, completionStatus, io.bytesTransferred);
DEBUG_LOG("-> %u bytes read, error %d\n", io.bytesTransferred, io.unixError == 0 ? 0 : wibo::lastError);
return io.unixError == 0;

View File

@@ -4,6 +4,7 @@
#include "handles.h"
#include "mimalloc.h"
#include <condition_variable>
#include <pthread.h>
namespace kernel32 {
@@ -30,6 +31,8 @@ struct FileObject : FsObject {
bool overlapped = false;
bool appendOnly = false;
bool isPipe = false;
// Used to notify overlapped operations without an event handle
std::condition_variable overlappedCv;
explicit FileObject(int fd) : FsObject(kType, fd) {
if (fd >= 0) {
@@ -99,18 +102,6 @@ struct MutexObject final : WaitableObject {
bool abandoned = false; // Owner exited without releasing
MutexObject() : WaitableObject(kType) { signaled = true; }
void noteOwnerExit(/*pthread_t tid or emu tid*/) {
std::lock_guard lk(m);
if (ownerValid /* && matches tid if you store emu id */) {
ownerValid = false;
recursionCount = 0;
abandoned = true;
signaled = true;
cv.notify_one();
notifyWaiters(true);
}
}
};
struct EventObject final : WaitableObject {

View File

@@ -2,24 +2,32 @@
#include "context.h"
#include "errors.h"
#include "internal.h"
#include "overlapped_util.h"
#include "synchapi.h"
#include <mutex>
namespace kernel32 {
BOOL WIN_FUNC GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred,
BOOL bWait) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("GetOverlappedResult(%p, %p, %p, %d)\n", hFile, lpOverlapped, lpNumberOfBytesTransferred, bWait);
(void)hFile;
if (!lpOverlapped) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
if (bWait && lpOverlapped->Internal == STATUS_PENDING &&
kernel32::detail::shouldSignalOverlappedEvent(lpOverlapped)) {
if (bWait && lpOverlapped->Internal == STATUS_PENDING) {
if (HANDLE waitHandle = kernel32::detail::normalizedOverlappedEventHandle(lpOverlapped)) {
WaitForSingleObject(waitHandle, INFINITE);
} else if (auto file = wibo::handles().getAs<FileObject>(hFile)) {
std::unique_lock lk(file->m);
file->overlappedCv.wait(lk, [&] { return lpOverlapped->Internal != STATUS_PENDING; });
} else {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
}

View File

@@ -25,7 +25,7 @@ namespace kernel32 {
namespace {
class NamedPipeInstance;
struct NamedPipeInstance;
void configureInheritability(int fd, bool inherit) {
if (fd < 0) {
@@ -112,17 +112,17 @@ struct NamedPipeState : ObjectBase {
DWORD defaultTimeout = 0;
DWORD maxInstances = PIPE_UNLIMITED_INSTANCES;
uint32_t instanceCount = 0;
std::vector<class NamedPipeInstance *> instances;
std::vector<NamedPipeInstance *> instances;
explicit NamedPipeState(std::string k) : ObjectBase(kType), key(std::move(k)) {}
~NamedPipeState() override { wibo::g_namespace.remove(this); }
void registerInstance(class NamedPipeInstance *inst) {
void registerInstance(NamedPipeInstance *inst) {
std::lock_guard lk(mutex);
instances.push_back(inst);
}
void unregisterInstance(class NamedPipeInstance *inst) {
void unregisterInstance(NamedPipeInstance *inst) {
std::lock_guard lk(mutex);
auto it = std::find(instances.begin(), instances.end(), inst);
if (it != instances.end()) {
@@ -194,12 +194,8 @@ struct NamedPipeInstance final : FileObject {
std::lock_guard lk(connectMutex);
localCompanion = companionFd;
companionFd = -1;
if (pendingOverlapped) {
pendingOverlapped->Internal = STATUS_PIPE_BROKEN;
pendingOverlapped->InternalHigh = 0;
kernel32::detail::signalOverlappedEvent(pendingOverlapped);
pendingOverlapped = nullptr;
}
kernel32::detail::signalOverlappedEvent(this, pendingOverlapped, STATUS_PIPE_BROKEN, 0);
pendingOverlapped = nullptr;
connectPending = false;
connectCv.notify_all();
}
@@ -208,8 +204,6 @@ struct NamedPipeInstance final : FileObject {
}
if (state) {
state->unregisterInstance(this);
}
if (state) {
state->releaseInstance();
}
}
@@ -233,24 +227,19 @@ struct NamedPipeInstance final : FileObject {
}
int takeCompanion() {
std::unique_lock lk(connectMutex);
std::lock_guard lk(connectMutex);
if (companionFd < 0 || clientConnected) {
return -1;
}
int fd = companionFd;
companionFd = -1;
clientConnected = true;
if (pendingOverlapped) {
pendingOverlapped->Internal = STATUS_SUCCESS;
pendingOverlapped->InternalHigh = 0;
kernel32::detail::signalOverlappedEvent(pendingOverlapped);
pendingOverlapped = nullptr;
}
kernel32::detail::signalOverlappedEvent(this, pendingOverlapped, STATUS_SUCCESS, 0);
pendingOverlapped = nullptr;
if (connectPending) {
connectPending = false;
connectCv.notify_all();
}
lk.unlock();
return fd;
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "context.h"
#include "errors.h"
#include "handles.h"
#include "internal.h"
#include "minwinbase.h"
@@ -9,41 +9,30 @@
namespace kernel32::detail {
inline bool shouldSignalOverlappedEvent(const OVERLAPPED *ov) {
if (!ov) {
return false;
}
auto raw = reinterpret_cast<uintptr_t>(ov->hEvent);
return (raw & 1U) == 0 && raw != 0;
}
inline HANDLE normalizedOverlappedEventHandle(const OVERLAPPED *ov) {
if (!ov) {
if (!ov || (reinterpret_cast<uintptr_t>(ov->hEvent) & 1U) != 0) {
return nullptr;
}
auto raw = reinterpret_cast<uintptr_t>(ov->hEvent);
raw &= ~static_cast<uintptr_t>(1);
return reinterpret_cast<HANDLE>(raw);
return ov->hEvent;
}
inline void signalOverlappedEvent(OVERLAPPED *ov) {
if (!shouldSignalOverlappedEvent(ov)) {
return;
inline void signalOverlappedEvent(FileObject *file, OVERLAPPED *ov, NTSTATUS status, size_t bytesTransferred) {
if (ov) {
ov->Internal = status;
ov->InternalHigh = static_cast<ULONG_PTR>(bytesTransferred);
}
HANDLE handle = normalizedOverlappedEventHandle(ov);
if (handle) {
if (HANDLE handle = normalizedOverlappedEventHandle(ov)) {
if (auto ev = wibo::handles().getAs<EventObject>(handle)) {
ev->set();
}
}
if (file) {
file->overlappedCv.notify_all();
}
}
inline void resetOverlappedEvent(OVERLAPPED *ov) {
if (!ov) {
return;
}
HANDLE handle = normalizedOverlappedEventHandle(ov);
if (handle) {
if (HANDLE handle = normalizedOverlappedEventHandle(ov)) {
if (auto ev = wibo::handles().getAs<EventObject>(handle)) {
ev->reset();
}

View File

@@ -176,11 +176,7 @@ void ThreadPoolBackend::workerLoop() {
void ThreadPoolBackend::processRequest(const AsyncRequest &req) {
if (!req.file || !req.file->valid()) {
if (req.overlapped) {
req.overlapped->Internal = STATUS_INVALID_HANDLE;
req.overlapped->InternalHigh = 0;
kernel32::detail::signalOverlappedEvent(req.overlapped);
}
kernel32::detail::signalOverlappedEvent(req.file.get(), req.overlapped, STATUS_INVALID_HANDLE, 0);
return;
}
@@ -206,11 +202,7 @@ void ThreadPoolBackend::processRequest(const AsyncRequest &req) {
completionStatus = STATUS_END_OF_FILE;
}
if (req.overlapped) {
req.overlapped->Internal = completionStatus;
req.overlapped->InternalHigh = bytesTransferred;
kernel32::detail::signalOverlappedEvent(req.overlapped);
}
kernel32::detail::signalOverlappedEvent(req.file.get(), req.overlapped, completionStatus, bytesTransferred);
}
} // namespace

View File

@@ -201,30 +201,25 @@ void IoUringBackend::handleCompletion(struct io_uring_cqe *cqe) {
return;
}
OVERLAPPED *ov = req->overlapped;
if (ov) {
if (cqe->res >= 0) {
ov->InternalHigh = static_cast<ULONG_PTR>(cqe->res);
if (req->kind == AsyncRequest::Kind::Read && cqe->res == 0) {
ov->Internal = req->isPipe ? STATUS_PIPE_BROKEN : STATUS_END_OF_FILE;
} else {
ov->Internal = STATUS_SUCCESS;
}
NTSTATUS completionStatus = STATUS_SUCCESS;
size_t bytesTransferred = 0;
if (cqe->res >= 0) {
bytesTransferred = static_cast<size_t>(cqe->res);
if (req->kind == AsyncRequest::Kind::Read && cqe->res == 0) {
completionStatus = req->isPipe ? STATUS_PIPE_BROKEN : STATUS_END_OF_FILE;
}
} else {
int err = -cqe->res;
if (err == EPIPE) {
completionStatus = STATUS_PIPE_BROKEN;
} else {
int err = -cqe->res;
ov->InternalHigh = 0;
if (err == EPIPE) {
ov->Internal = STATUS_PIPE_BROKEN;
} else {
NTSTATUS status = wibo::statusFromErrno(err);
if (status == STATUS_SUCCESS) {
status = STATUS_UNEXPECTED_IO_ERROR;
}
ov->Internal = status;
completionStatus = wibo::statusFromErrno(err);
if (completionStatus == STATUS_SUCCESS) {
completionStatus = STATUS_UNEXPECTED_IO_ERROR;
}
}
kernel32::detail::signalOverlappedEvent(ov);
}
kernel32::detail::signalOverlappedEvent(req->file.get(), req->overlapped, completionStatus, bytesTransferred);
delete req;
mPending.fetch_sub(1, std::memory_order_acq_rel);

View File

@@ -5,235 +5,465 @@
#define STATUS_PENDING ((DWORD)0x00000103)
#endif
static const char *kFilename = "overlapped_test.tmp";
static char g_tempFilename[MAX_PATH];
struct GetOverlappedWaitArgs {
HANDLE handle;
OVERLAPPED *ov;
DWORD bytesTransferred;
BOOL result;
DWORD error;
};
static DWORD WINAPI getoverlapped_wait_thread(LPVOID param) {
struct GetOverlappedWaitArgs *args = (struct GetOverlappedWaitArgs *)param;
args->bytesTransferred = 0xFFFFFFFFu;
SetLastError(0xDEADBEEFu);
args->result = GetOverlappedResult(args->handle, args->ov, &args->bytesTransferred, TRUE);
args->error = args->result ? ERROR_SUCCESS : GetLastError();
return 0;
}
struct SyncReaderArgs {
HANDLE pipe;
OVERLAPPED *ov;
HANDLE startedEvent;
DWORD expectedBytes;
DWORD bytesRead;
BOOL readSucceeded;
};
static DWORD WINAPI sync_reader_thread(LPVOID param) {
struct SyncReaderArgs *args = (struct SyncReaderArgs *)param;
char buffer[32] = {0};
args->ov->Internal = STATUS_PENDING;
args->ov->InternalHigh = 0;
TEST_CHECK(SetEvent(args->startedEvent));
args->bytesRead = 0;
args->readSucceeded = ReadFile(args->pipe, buffer, args->expectedBytes, &args->bytesRead, args->ov);
return args->readSucceeded ? 0 : GetLastError();
}
struct SyncWriterArgs {
const char *pipeName;
HANDLE serverReadyEvent;
};
static DWORD WINAPI sync_writer_thread(LPVOID param) {
struct SyncWriterArgs *args = (struct SyncWriterArgs *)param;
TEST_CHECK(WaitForSingleObject(args->serverReadyEvent, 1000) == WAIT_OBJECT_0);
HANDLE client = CreateFileA(args->pipeName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
TEST_CHECK_MSG(client != INVALID_HANDLE_VALUE, "CreateFileA(client) failed: %lu", GetLastError());
Sleep(200);
static const char msg[] = "READY";
DWORD written = 0;
TEST_CHECK(WriteFile(client, msg, (DWORD)(sizeof(msg) - 1), &written, NULL));
TEST_CHECK_EQ(sizeof(msg) - 1, written);
CloseHandle(client);
return 0;
}
struct ManualEventWriterArgs {
const char *pipeName;
HANDLE serverReadyEvent;
HANDLE proceedEvent;
};
static DWORD WINAPI manual_event_writer_thread(LPVOID param) {
struct ManualEventWriterArgs *args = (struct ManualEventWriterArgs *)param;
TEST_CHECK(WaitForSingleObject(args->serverReadyEvent, 1000) == WAIT_OBJECT_0);
HANDLE client = CreateFileA(args->pipeName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
TEST_CHECK_MSG(client != INVALID_HANDLE_VALUE, "CreateFileA(client) failed: %lu", GetLastError());
TEST_CHECK(WaitForSingleObject(args->proceedEvent, 1000) == WAIT_OBJECT_0);
static const char payload[] = "PING!";
DWORD written = 0;
TEST_CHECK(WriteFile(client, payload, (DWORD)(sizeof(payload) - 1), &written, NULL));
TEST_CHECK_EQ(sizeof(payload) - 1, written);
CloseHandle(client);
return 0;
}
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);
HANDLE file = CreateFileA(g_tempFilename, 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));
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);
HANDLE file = CreateFileA(g_tempFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
OVERLAPPED ov = {0};
ov.Offset = 5;
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);
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);
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;
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));
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);
HANDLE file = CreateFileA(g_tempFilename, 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);
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());
}
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, 1000) == WAIT_OBJECT_0);
TEST_CHECK(WaitForSingleObject(ov.hEvent, 1000) == 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);
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));
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
}
static void test_overlapped_read_without_event(void) {
HANDLE file = CreateFileA(g_tempFilename, 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 = 4;
char buffer[16] = {0};
BOOL issued = ReadFile(file, buffer, 8, NULL, &ov);
if (!issued) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
DWORD transferred = 0xFFFFFFFFU;
BOOL ready = GetOverlappedResult(file, &ov, &transferred, FALSE);
if (!ready) {
TEST_CHECK_EQ(ERROR_IO_INCOMPLETE, GetLastError());
TEST_CHECK_EQ(0xFFFFFFFFU, transferred); // untouched while pending
} else {
TEST_CHECK_EQ(8U, transferred);
}
}
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, TRUE));
TEST_CHECK_EQ(8U, transferred);
buffer[8] = '\0';
TEST_CHECK_STR_EQ("456789AB", buffer);
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);
HANDLE file = CreateFileA(g_tempFilename, 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);
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());
}
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, 1000) == WAIT_OBJECT_0);
TEST_CHECK(WaitForSingleObject(ov.hEvent, 1000) == 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);
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));
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
}
static void test_getoverlappedresult_manual_event_signal(void) {
static const char *pipeName = "\\\\.\\pipe\\wibo_manual_event";
HANDLE pipe = CreateNamedPipeA(pipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT, 1, 0, 0, 0, NULL);
TEST_CHECK_MSG(pipe != INVALID_HANDLE_VALUE, "CreateNamedPipeA failed: %lu", GetLastError());
HANDLE serverReady = CreateEventA(NULL, TRUE, FALSE, NULL);
HANDLE proceedWrite = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(serverReady != NULL && proceedWrite != NULL);
struct ManualEventWriterArgs writerArgs = {pipeName, serverReady, proceedWrite};
HANDLE writerThread = CreateThread(NULL, 0, manual_event_writer_thread, &writerArgs, 0, NULL);
TEST_CHECK(writerThread != NULL);
OVERLAPPED connectOv = {0};
connectOv.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(connectOv.hEvent != NULL);
BOOL connectIssued = ConnectNamedPipe(pipe, &connectOv);
if (!connectIssued) {
DWORD err = GetLastError();
TEST_CHECK_MSG(err == ERROR_IO_PENDING || err == ERROR_PIPE_CONNECTED, "ConnectNamedPipe err=%lu", err);
}
TEST_CHECK(SetEvent(serverReady));
if (!connectIssued) {
TEST_CHECK(WaitForSingleObject(connectOv.hEvent, 1000) == WAIT_OBJECT_0);
}
CloseHandle(connectOv.hEvent);
OVERLAPPED ov = {0};
ov.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(ov.hEvent != NULL);
char buffer[8] = {0};
BOOL issued = ReadFile(pipe, buffer, sizeof(buffer), NULL, &ov);
TEST_CHECK(!issued);
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
DWORD transferred = 0xDEADBEEFu;
TEST_CHECK(SetEvent(ov.hEvent));
SetLastError(0xDEADBEEFu);
DWORD start = GetTickCount();
BOOL ready = GetOverlappedResult(pipe, &ov, &transferred, TRUE);
DWORD elapsed = GetTickCount() - start;
TEST_CHECK_MSG(elapsed < 1000, "GetOverlappedResult waited unexpectedly long (%lu ms)", elapsed);
if (!ready) {
TEST_CHECK_EQ(ERROR_IO_INCOMPLETE, GetLastError());
TEST_CHECK_EQ(0xDEADBEEFu, transferred);
} else {
TEST_CHECK_EQ(STATUS_PENDING, (DWORD)ov.Internal);
}
TEST_CHECK(ResetEvent(ov.hEvent));
TEST_CHECK(SetEvent(proceedWrite));
TEST_CHECK(WaitForSingleObject(writerThread, 1000) == WAIT_OBJECT_0);
TEST_CHECK(WaitForSingleObject(ov.hEvent, 1000) == WAIT_OBJECT_0);
DWORD finalTransferred = 0;
TEST_CHECK(GetOverlappedResult(pipe, &ov, &finalTransferred, TRUE));
TEST_CHECK_EQ(5U, finalTransferred);
TEST_CHECK(CloseHandle(writerThread));
TEST_CHECK(CloseHandle(proceedWrite));
TEST_CHECK(CloseHandle(serverReady));
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(pipe));
}
static void test_getoverlappedresult_non_overlapped_handle(void) {
static const char *pipeName = "\\\\.\\pipe\\wibo_sync_pipe";
HANDLE pipe = CreateNamedPipeA(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 0, 0, 0, NULL);
TEST_CHECK_MSG(pipe != INVALID_HANDLE_VALUE, "CreateNamedPipeA failed: %lu", GetLastError());
HANDLE serverReady = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(serverReady != NULL);
struct SyncWriterArgs writerArgs = {pipeName, serverReady};
HANDLE writerThread = CreateThread(NULL, 0, sync_writer_thread, &writerArgs, 0, NULL);
TEST_CHECK(writerThread != NULL);
TEST_CHECK(SetEvent(serverReady));
BOOL connected = ConnectNamedPipe(pipe, NULL);
if (!connected) {
DWORD err = GetLastError();
TEST_CHECK_EQ(ERROR_PIPE_CONNECTED, err);
}
HANDLE readStarted = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(readStarted != NULL);
OVERLAPPED ov = {0};
struct SyncReaderArgs readerArgs = {pipe, &ov, readStarted, 5, 0, FALSE};
HANDLE readerThread = CreateThread(NULL, 0, sync_reader_thread, &readerArgs, 0, NULL);
TEST_CHECK(readerThread != NULL);
TEST_CHECK(WaitForSingleObject(readStarted, 1000) == WAIT_OBJECT_0);
struct GetOverlappedWaitArgs waitArgs = {pipe, &ov, 0, FALSE, 0};
HANDLE waitThread = CreateThread(NULL, 0, getoverlapped_wait_thread, &waitArgs, 0, NULL);
TEST_CHECK(waitThread != NULL);
TEST_CHECK(WaitForSingleObject(readerThread, 5000) == WAIT_OBJECT_0);
TEST_CHECK(readerArgs.readSucceeded);
TEST_CHECK_EQ(5U, readerArgs.bytesRead);
TEST_CHECK(WaitForSingleObject(waitThread, 5000) == WAIT_OBJECT_0);
TEST_CHECK(waitArgs.result);
TEST_CHECK_EQ(ERROR_SUCCESS, waitArgs.error);
TEST_CHECK_EQ(5U, waitArgs.bytesTransferred);
TEST_CHECK(WaitForSingleObject(writerThread, 5000) == WAIT_OBJECT_0);
TEST_CHECK(CloseHandle(waitThread));
TEST_CHECK(CloseHandle(readerThread));
TEST_CHECK(CloseHandle(readStarted));
TEST_CHECK(CloseHandle(writerThread));
TEST_CHECK(CloseHandle(serverReady));
TEST_CHECK(CloseHandle(pipe));
}
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());
TEST_CHECK_EQ(0U, transferred); // No update if the operation is still pending
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());
TEST_CHECK_EQ(0U, transferred); // No update if the operation is still pending
}
static void test_overlapped_multiple_reads(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);
HANDLE file = CreateFileA(g_tempFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
OVERLAPPED ov1 = {0};
OVERLAPPED ov2 = {0};
ov1.Offset = 0;
ov2.Offset = 16;
ov1.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
ov2.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(ov1.hEvent != NULL);
TEST_CHECK(ov2.hEvent != NULL);
OVERLAPPED ov1 = {0};
OVERLAPPED ov2 = {0};
ov1.Offset = 0;
ov2.Offset = 16;
ov1.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
ov2.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
TEST_CHECK(ov1.hEvent != NULL);
TEST_CHECK(ov2.hEvent != NULL);
char head[8] = {0};
char tail[8] = {0};
char head[8] = {0};
char tail[8] = {0};
BOOL issued1 = ReadFile(file, head, 5, NULL, &ov1);
if (!issued1) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
BOOL issued1 = ReadFile(file, head, 5, NULL, &ov1);
if (!issued1) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
BOOL issued2 = ReadFile(file, tail, 5, NULL, &ov2);
if (!issued2) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
BOOL issued2 = ReadFile(file, tail, 5, NULL, &ov2);
if (!issued2) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
HANDLE events[2] = {ov1.hEvent, ov2.hEvent};
DWORD waitResult = WaitForMultipleObjects(2, events, TRUE, 1000);
TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult);
HANDLE events[2] = {ov1.hEvent, ov2.hEvent};
DWORD waitResult = WaitForMultipleObjects(2, events, TRUE, 1000);
TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult);
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov1, &transferred, FALSE));
TEST_CHECK_EQ(5U, transferred);
head[5] = '\0';
TEST_CHECK_STR_EQ("01234", head);
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov1, &transferred, FALSE));
TEST_CHECK_EQ(5U, transferred);
head[5] = '\0';
TEST_CHECK_STR_EQ("01234", head);
transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov2, &transferred, FALSE));
TEST_CHECK_EQ(5U, transferred);
tail[5] = '\0';
TEST_CHECK_STR_EQ("GHIJK", tail);
transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov2, &transferred, FALSE));
TEST_CHECK_EQ(5U, transferred);
tail[5] = '\0';
TEST_CHECK_STR_EQ("GHIJK", tail);
TEST_CHECK(CloseHandle(ov2.hEvent));
TEST_CHECK(CloseHandle(ov1.hEvent));
TEST_CHECK(CloseHandle(file));
TEST_CHECK(CloseHandle(ov2.hEvent));
TEST_CHECK(CloseHandle(ov1.hEvent));
TEST_CHECK(CloseHandle(file));
}
static void test_getoverlappedresult_wait(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);
HANDLE file = CreateFileA(g_tempFilename, 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 = 20;
ov.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
TEST_CHECK(ov.hEvent != NULL);
OVERLAPPED ov = {0};
ov.Offset = 20;
ov.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
TEST_CHECK(ov.hEvent != NULL);
char buffer[8] = {0};
BOOL issued = ReadFile(file, buffer, 6, NULL, &ov);
if (!issued) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
char buffer[8] = {0};
BOOL issued = ReadFile(file, buffer, 6, NULL, &ov);
if (!issued) {
TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError());
}
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, TRUE));
TEST_CHECK_EQ(6U, transferred);
buffer[6] = '\0';
TEST_CHECK_STR_EQ("KLMNOP", buffer);
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, TRUE));
TEST_CHECK_EQ(6U, transferred);
buffer[6] = '\0';
TEST_CHECK_STR_EQ("KLMNOP", buffer);
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
TEST_CHECK(CloseHandle(ov.hEvent));
TEST_CHECK(CloseHandle(file));
}
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);
HANDLE file = CreateFileA(g_tempFilename, 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);
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, 1000) == WAIT_OBJECT_0);
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, 1000) == WAIT_OBJECT_0);
DWORD transferred = 0;
TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, FALSE));
TEST_CHECK_EQ((DWORD)(sizeof(patch) - 1), transferred);
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));
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);
HANDLE verify = CreateFileA(g_tempFilename, 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(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));
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_multiple_reads();
test_getoverlappedresult_wait();
test_overlapped_write();
TEST_CHECK(DeleteFileA(kFilename));
return 0;
char tempPath[MAX_PATH] = {0};
DWORD len = GetTempPathA((DWORD)sizeof(tempPath), tempPath);
TEST_CHECK_MSG(len > 0 && len < sizeof(tempPath), "GetTempPathA failed: %lu", GetLastError());
TEST_CHECK_MSG(GetTempFileNameA(tempPath, "wbo", 0, g_tempFilename) != 0,
"GetTempFileNameA failed: %lu", GetLastError());
DeleteFileA(g_tempFilename);
write_fixture_file();
test_synchronous_overlapped_read();
test_overlapped_read_with_event();
test_overlapped_read_without_event();
test_overlapped_eof();
test_getoverlappedresult_pending();
test_overlapped_multiple_reads();
test_getoverlappedresult_wait();
test_overlapped_write();
test_getoverlappedresult_manual_event_signal();
test_getoverlappedresult_non_overlapped_handle();
TEST_CHECK(DeleteFileA(g_tempFilename));
return 0;
}