#include "test_assert.h" #include #ifndef STATUS_PENDING #define STATUS_PENDING ((DWORD)0x00000103) #endif 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(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)); } static void test_overlapped_requires_overlapped_structure(void) { HANDLE file = CreateFileA(g_tempFilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); TEST_CHECK(file != INVALID_HANDLE_VALUE); char buffer[8] = {0}; DWORD bytesRead = 0xFFFFFFFFu; SetLastError(0); TEST_CHECK(!ReadFile(file, buffer, sizeof(buffer), &bytesRead, NULL)); TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); TEST_CHECK_EQ(0U, bytesRead); static const char payload[] = "data"; DWORD bytesWritten = 0xFFFFFFFFu; SetLastError(0); TEST_CHECK(!WriteFile(file, payload, (DWORD)(sizeof(payload) - 1), &bytesWritten, NULL)); TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); TEST_CHECK_EQ(0U, bytesWritten); TEST_CHECK(CloseHandle(file)); } static void test_synchronous_overlapped_read(void) { 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; 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(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); 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); 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_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(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); 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); 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_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 } static void test_overlapped_multiple_reads(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 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}; 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()); } 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); 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)); } static void test_getoverlappedresult_wait(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 = 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()); } 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)); } static void test_overlapped_write(void) { 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); 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); TEST_CHECK(CloseHandle(ov.hEvent)); TEST_CHECK(CloseHandle(file)); 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(CloseHandle(verify)); } int main(void) { 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_requires_overlapped_structure(); 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; }