Files
wibo/test/test_overlapped_io.c

493 lines
17 KiB
C

#include "test_assert.h"
#include <windows.h>
#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;
}