Implement ConnectNamedPipe

This commit is contained in:
2025-10-23 00:39:42 -06:00
parent 8e532ccf78
commit 4d5caf91e1
5 changed files with 212 additions and 4 deletions

View File

@@ -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)

View File

@@ -5,11 +5,13 @@
#include "errors.h"
#include "handles.h"
#include "internal.h"
#include "overlapped_util.h"
#include "strutil.h"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <condition_variable>
#include <fcntl.h>
#include <mutex>
#include <optional>
@@ -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<NamedPipeState> 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<NamedPipeInstance *>(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

View File

@@ -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);

View File

@@ -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

View File

@@ -6,6 +6,38 @@
#include <string.h>
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;
}