diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index d9872e0..ec00c3c 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -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(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(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; diff --git a/dll/kernel32/internal.h b/dll/kernel32/internal.h index 7c919e5..66e120a 100644 --- a/dll/kernel32/internal.h +++ b/dll/kernel32/internal.h @@ -4,6 +4,7 @@ #include "handles.h" #include "mimalloc.h" +#include #include 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 { diff --git a/dll/kernel32/ioapiset.cpp b/dll/kernel32/ioapiset.cpp index cf3d085..d279409 100644 --- a/dll/kernel32/ioapiset.cpp +++ b/dll/kernel32/ioapiset.cpp @@ -2,24 +2,32 @@ #include "context.h" #include "errors.h" +#include "internal.h" #include "overlapped_util.h" #include "synchapi.h" +#include + 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(hFile)) { + std::unique_lock lk(file->m); + file->overlappedCv.wait(lk, [&] { return lpOverlapped->Internal != STATUS_PENDING; }); + } else { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; } } diff --git a/dll/kernel32/namedpipeapi.cpp b/dll/kernel32/namedpipeapi.cpp index 69e8127..fdbe659 100644 --- a/dll/kernel32/namedpipeapi.cpp +++ b/dll/kernel32/namedpipeapi.cpp @@ -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 instances; + std::vector 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; } diff --git a/dll/kernel32/overlapped_util.h b/dll/kernel32/overlapped_util.h index bf1cfb1..1c353e2 100644 --- a/dll/kernel32/overlapped_util.h +++ b/dll/kernel32/overlapped_util.h @@ -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(ov->hEvent); - return (raw & 1U) == 0 && raw != 0; -} - inline HANDLE normalizedOverlappedEventHandle(const OVERLAPPED *ov) { - if (!ov) { + if (!ov || (reinterpret_cast(ov->hEvent) & 1U) != 0) { return nullptr; } - auto raw = reinterpret_cast(ov->hEvent); - raw &= ~static_cast(1); - return reinterpret_cast(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(bytesTransferred); } - HANDLE handle = normalizedOverlappedEventHandle(ov); - if (handle) { + if (HANDLE handle = normalizedOverlappedEventHandle(ov)) { if (auto ev = wibo::handles().getAs(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(handle)) { ev->reset(); } diff --git a/src/async_io_threadpool.cpp b/src/async_io_threadpool.cpp index c427a7e..44382ca 100644 --- a/src/async_io_threadpool.cpp +++ b/src/async_io_threadpool.cpp @@ -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 diff --git a/src/async_io_uring.cpp b/src/async_io_uring.cpp index b319e7d..13163e5 100644 --- a/src/async_io_uring.cpp +++ b/src/async_io_uring.cpp @@ -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(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(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); diff --git a/test/test_overlapped_io.c b/test/test_overlapped_io.c index 95ada6f..71836b0 100644 --- a/test/test_overlapped_io.c +++ b/test/test_overlapped_io.c @@ -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; }