diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 5856ecc..b2e31b6 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -200,6 +200,8 @@ void *resolveByName(const char *name) { return (void *)kernel32::CreatePipe; if (strcmp(name, "CreateNamedPipeA") == 0) return (void *)kernel32::CreateNamedPipeA; + if (strcmp(name, "ConnectNamedPipe") == 0) + return (void *)kernel32::ConnectNamedPipe; // winbase.h if (strcmp(name, "FindAtomA") == 0) diff --git a/dll/kernel32/namedpipeapi.cpp b/dll/kernel32/namedpipeapi.cpp index 91c4d95..69e8127 100644 --- a/dll/kernel32/namedpipeapi.cpp +++ b/dll/kernel32/namedpipeapi.cpp @@ -5,11 +5,13 @@ #include "errors.h" #include "handles.h" #include "internal.h" +#include "overlapped_util.h" #include "strutil.h" #include #include #include +#include #include #include #include @@ -174,6 +176,10 @@ struct NamedPipeInstance final : FileObject { DWORD accessMode; DWORD pipeMode; bool clientConnected = false; + bool connectPending = false; + LPOVERLAPPED pendingOverlapped = nullptr; + std::mutex connectMutex; + std::condition_variable connectCv; NamedPipeInstance(int fd, Pin st, int companion, DWORD open, DWORD mode) : FileObject(fd), state(std::move(st)), companionFd(companion), accessMode(open), pipeMode(mode) { @@ -183,9 +189,22 @@ struct NamedPipeInstance final : FileObject { } ~NamedPipeInstance() override { - if (companionFd >= 0) { - close(companionFd); + int localCompanion = -1; + { + 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; + } + connectPending = false; + connectCv.notify_all(); + } + if (localCompanion >= 0) { + close(localCompanion); } if (state) { state->unregisterInstance(this); @@ -195,7 +214,8 @@ struct NamedPipeInstance final : FileObject { } } - bool canAcceptClient(DWORD desiredAccess) const { + bool canAcceptClient(DWORD desiredAccess) { + std::lock_guard lk(connectMutex); if (companionFd < 0 || clientConnected) { return false; } @@ -213,16 +233,29 @@ struct NamedPipeInstance final : FileObject { } int takeCompanion() { - if (companionFd < 0) { + std::unique_lock 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; + } + if (connectPending) { + connectPending = false; + connectCv.notify_all(); + } + lk.unlock(); return fd; } void restoreCompanion(int fd) { + std::lock_guard lk(connectMutex); companionFd = fd; if (fd >= 0) { clientConnected = false; @@ -502,4 +535,64 @@ HANDLE WIN_FUNC CreateNamedPipeA(LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMo return wibo::handles().alloc(std::move(pipeObj), grantedAccess, handleFlags); } +BOOL WIN_FUNC ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("ConnectNamedPipe(%p, %p)\n", hNamedPipe, lpOverlapped); + + auto pin = wibo::handles().get(hNamedPipe); + auto *pipe = pin ? dynamic_cast(pin.get()) : nullptr; + if (!pipe) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + + const bool isOverlappedHandle = pipe->overlapped; + if (isOverlappedHandle && lpOverlapped == nullptr) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + + std::unique_lock lock(pipe->connectMutex); + + if (pipe->clientConnected) { + wibo::lastError = ERROR_PIPE_CONNECTED; + return FALSE; + } + + if (pipe->companionFd < 0) { + wibo::lastError = ERROR_PIPE_BUSY; + return FALSE; + } + + if (pipe->connectPending) { + wibo::lastError = ERROR_PIPE_LISTENING; + return FALSE; + } + + if ((pipe->pipeMode & PIPE_NOWAIT) != 0) { + wibo::lastError = ERROR_PIPE_LISTENING; + return FALSE; + } + + if (isOverlappedHandle) { + pipe->connectPending = true; + pipe->pendingOverlapped = lpOverlapped; + lpOverlapped->Internal = STATUS_PENDING; + lpOverlapped->InternalHigh = 0; + kernel32::detail::resetOverlappedEvent(lpOverlapped); + lock.unlock(); + wibo::lastError = ERROR_IO_PENDING; + return FALSE; + } + + pipe->connectPending = true; + pipe->connectCv.wait(lock, [&]() { return pipe->clientConnected || pipe->companionFd < 0; }); + pipe->connectPending = false; + if (!pipe->clientConnected) { + wibo::lastError = ERROR_NO_DATA; + return FALSE; + } + return TRUE; +} + } // namespace kernel32 diff --git a/dll/kernel32/namedpipeapi.h b/dll/kernel32/namedpipeapi.h index c5baf34..bfff0e7 100644 --- a/dll/kernel32/namedpipeapi.h +++ b/dll/kernel32/namedpipeapi.h @@ -9,6 +9,7 @@ BOOL WIN_FUNC CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRI HANDLE WIN_FUNC CreateNamedPipeA(LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes); +BOOL WIN_FUNC ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped); bool tryCreateFileNamedPipeA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE &outHandle); diff --git a/src/errors.h b/src/errors.h index 0f52d25..ca34bd5 100644 --- a/src/errors.h +++ b/src/errors.h @@ -27,6 +27,9 @@ #define ERROR_IO_PENDING 997 #define ERROR_OPERATION_ABORTED 995 #define ERROR_PIPE_BUSY 231 +#define ERROR_NO_DATA 232 +#define ERROR_PIPE_CONNECTED 535 +#define ERROR_PIPE_LISTENING 536 #define ERROR_NONE_MAPPED 1332 #define ERROR_RESOURCE_DATA_NOT_FOUND 1812 #define ERROR_RESOURCE_TYPE_NOT_FOUND 1813 diff --git a/test/test_namedpipe.c b/test/test_namedpipe.c index 60f3168..117115a 100644 --- a/test/test_namedpipe.c +++ b/test/test_namedpipe.c @@ -6,6 +6,38 @@ #include static const char *kPipeName = "\\\\.\\pipe\\wibo_test_namedpipe"; +static const char *kPipeNameConnect = "\\\\.\\pipe\\wibo_test_namedpipe_connect"; +static const char *kPipeNameNowait = "\\\\.\\pipe\\wibo_test_namedpipe_nowait"; +static const char *kPipeNameOverlapped = "\\\\.\\pipe\\wibo_test_namedpipe_overlapped"; + +struct ConnectThreadArgs { + const char *pipeName; + const char *message; + DWORD delayMs; + HANDLE client; + DWORD error; +}; + +static DWORD WINAPI client_connect_thread(LPVOID parameter) { + struct ConnectThreadArgs *args = (struct ConnectThreadArgs *)parameter; + args->client = INVALID_HANDLE_VALUE; + args->error = ERROR_SUCCESS; + if (args->delayMs) { + Sleep(args->delayMs); + } + HANDLE client = + CreateFileA(args->pipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + args->client = client; + if (client == INVALID_HANDLE_VALUE) { + args->error = GetLastError(); + return 0; + } + if (args->message) { + DWORD written = 0; + WriteFile(client, args->message, (DWORD)strlen(args->message), &written, NULL); + } + return 0; +} static void write_checked(HANDLE handle, const char *msg) { DWORD written = 0; @@ -70,5 +102,82 @@ int main(void) { TEST_CHECK(CloseHandle(client)); TEST_CHECK(CloseHandle(pipe)); + HANDLE connectPipe = CreateNamedPipeA(kPipeNameConnect, PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL); + TEST_CHECK(connectPipe != INVALID_HANDLE_VALUE); + + struct ConnectThreadArgs connectArgs = { + .pipeName = kPipeNameConnect, + .message = "hello", + .delayMs = 50, + .client = INVALID_HANDLE_VALUE, + .error = ERROR_SUCCESS, + }; + + HANDLE connectThread = CreateThread(NULL, 0, client_connect_thread, &connectArgs, 0, NULL); + TEST_CHECK(connectThread != NULL); + TEST_CHECK(ConnectNamedPipe(connectPipe, NULL)); + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(connectThread, 5000)); + TEST_CHECK(CloseHandle(connectThread)); + TEST_CHECK(connectArgs.client != INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_SUCCESS, connectArgs.error); + + read_checked(connectPipe, "hello"); + write_checked(connectPipe, "world"); + read_checked(connectArgs.client, "world"); + + SetLastError(0xdeadbeefu); + TEST_CHECK(!ConnectNamedPipe(connectPipe, NULL)); + TEST_CHECK_EQ(ERROR_PIPE_CONNECTED, GetLastError()); + + TEST_CHECK(CloseHandle(connectArgs.client)); + TEST_CHECK(CloseHandle(connectPipe)); + + HANDLE nowaitPipe = CreateNamedPipeA(kPipeNameNowait, PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, 1, 1024, 1024, 0, NULL); + TEST_CHECK(nowaitPipe != INVALID_HANDLE_VALUE); + SetLastError(0xdeadbeefu); + TEST_CHECK(!ConnectNamedPipe(nowaitPipe, NULL)); + TEST_CHECK_EQ(ERROR_PIPE_LISTENING, GetLastError()); + TEST_CHECK(CloseHandle(nowaitPipe)); + + HANDLE overlappedPipe = CreateNamedPipeA(kPipeNameOverlapped, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL); + TEST_CHECK(overlappedPipe != INVALID_HANDLE_VALUE); + + struct ConnectThreadArgs overlappedArgs = { + .pipeName = kPipeNameOverlapped, + .message = NULL, + .delayMs = 50, + .client = INVALID_HANDLE_VALUE, + .error = ERROR_SUCCESS, + }; + + OVERLAPPED ov = {0}; + ov.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL); + TEST_CHECK(ov.hEvent != NULL); + + HANDLE overlappedThread = CreateThread(NULL, 0, client_connect_thread, &overlappedArgs, 0, NULL); + TEST_CHECK(overlappedThread != NULL); + + SetLastError(0xdeadbeefu); + TEST_CHECK(!ConnectNamedPipe(overlappedPipe, &ov)); + TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError()); + + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(overlappedThread, 5000)); + TEST_CHECK(CloseHandle(overlappedThread)); + TEST_CHECK(overlappedArgs.client != INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_SUCCESS, overlappedArgs.error); + + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(ov.hEvent, 5000)); + + DWORD transferred = 0; + TEST_CHECK(GetOverlappedResult(overlappedPipe, &ov, &transferred, FALSE)); + TEST_CHECK_EQ((DWORD)0, transferred); + + TEST_CHECK(CloseHandle(overlappedArgs.client)); + TEST_CHECK(CloseHandle(ov.hEvent)); + TEST_CHECK(CloseHandle(overlappedPipe)); + return 0; }