diff --git a/CMakeLists.txt b/CMakeLists.txt index e180138..f70c4c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,6 +276,7 @@ if (WIBO_ENABLE_FIXTURE_TESTS) wibo_add_fixture_bin(NAME test_ntquery SOURCES test/test_ntquery.c) wibo_add_fixture_bin(NAME test_ntreadfile SOURCES test/test_ntreadfile.c) wibo_add_fixture_bin(NAME test_pipe_io SOURCES test/test_pipe_io.c) + wibo_add_fixture_bin(NAME test_namedpipe SOURCES test/test_namedpipe.c) wibo_add_fixture_bin(NAME test_sysdir SOURCES test/test_sysdir.c) # DLLs for fixture tests diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 9defeb9..5856ecc 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -198,6 +198,8 @@ void *resolveByName(const char *name) { // namedpipeapi.h if (strcmp(name, "CreatePipe") == 0) return (void *)kernel32::CreatePipe; + if (strcmp(name, "CreateNamedPipeA") == 0) + return (void *)kernel32::CreateNamedPipeA; // winbase.h if (strcmp(name, "FindAtomA") == 0) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index 9c23ac9..d9872e0 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -8,6 +8,7 @@ #include "files.h" #include "handles.h" #include "internal.h" +#include "namedpipeapi.h" #include "overlapped_util.h" #include "strutil.h" #include "timeutil.h" @@ -895,6 +896,13 @@ HANDLE WIN_FUNC CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSh return consoleHandle; } + HANDLE pipeHandle = INVALID_HANDLE_VALUE; + if (kernel32::tryCreateFileNamedPipeA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, pipeHandle)) { + DEBUG_LOG("CreateFileA(pipe=%s) -> %p (err=%u)\n", lpFileName, pipeHandle, wibo::lastError); + return pipeHandle; + } + std::filesystem::path hostPath = files::pathFromWindows(lpFileName); std::string hostPathStr = hostPath.string(); DEBUG_LOG("CreateFileA(filename=%s (%s), desiredAccess=0x%x, shareMode=%u, securityAttributes=%p, " diff --git a/dll/kernel32/internal.h b/dll/kernel32/internal.h index 140d418..7c919e5 100644 --- a/dll/kernel32/internal.h +++ b/dll/kernel32/internal.h @@ -23,7 +23,7 @@ struct FsObject : ObjectBase { explicit FsObject(ObjectType type, int fd) : ObjectBase(type), fd(fd) {} }; -struct FileObject final : FsObject { +struct FileObject : FsObject { static constexpr ObjectType kType = ObjectType::File; off_t filePos = 0; @@ -41,6 +41,8 @@ struct FileObject final : FsObject { } } } + + ~FileObject() override = default; }; struct DirectoryObject final : FsObject { diff --git a/dll/kernel32/namedpipeapi.cpp b/dll/kernel32/namedpipeapi.cpp index 2754668..91c4d95 100644 --- a/dll/kernel32/namedpipeapi.cpp +++ b/dll/kernel32/namedpipeapi.cpp @@ -5,14 +5,26 @@ #include "errors.h" #include "handles.h" #include "internal.h" +#include "strutil.h" +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include namespace kernel32 { namespace { +class NamedPipeInstance; + void configureInheritability(int fd, bool inherit) { if (fd < 0) { return; @@ -29,8 +41,289 @@ void configureInheritability(int fd, bool inherit) { fcntl(fd, F_SETFD, flags); } +struct ParsedPipeName { + std::string key; + std::u16string namespaceKey; +}; + +std::optional parsePipeName(LPCSTR name, DWORD &error) { + error = ERROR_SUCCESS; + if (!name) { + error = ERROR_PATH_NOT_FOUND; + return std::nullopt; + } + std::string_view input{name}; + if (input.empty()) { + error = ERROR_INVALID_NAME; + return std::nullopt; + } + if (input.size() > 256) { + error = ERROR_INVALID_PARAMETER; + return std::nullopt; + } + std::string lower; + lower.reserve(input.size()); + for (char ch : input) { + lower.push_back(static_cast(std::tolower(static_cast(ch)))); + } + constexpr std::string_view kLocalPrefix = "\\\\.\\pipe\\"; + constexpr std::string_view kNtPrefix = "\\\\?\\pipe\\"; + size_t prefixLen = 0; + if (lower.rfind(kLocalPrefix, 0) == 0) { + prefixLen = kLocalPrefix.size(); + } else if (lower.rfind(kNtPrefix, 0) == 0) { + prefixLen = kNtPrefix.size(); + } else { + // Not a pipe path; treat as non-match without error. + return std::nullopt; + } + std::string raw = std::string(input.substr(prefixLen)); + if (raw.empty()) { + error = ERROR_INVALID_HANDLE; + return std::nullopt; + } + if (raw.find('\\') != std::string::npos || raw.find('/') != std::string::npos) { + error = ERROR_INVALID_NAME; + return std::nullopt; + } + std::string key = lower.substr(prefixLen); + return ParsedPipeName{std::move(key), stringToUtf16(lower.substr(prefixLen))}; +} + +DWORD normalizeMaxInstances(DWORD value) { + if (value == 0) { + return 1; + } + if (value >= PIPE_UNLIMITED_INSTANCES) { + return PIPE_UNLIMITED_INSTANCES; + } + return value; +} + +struct NamedPipeState : ObjectBase { + static constexpr ObjectType kType = ObjectType::NamedPipe; + + std::mutex mutex; + std::string key; + DWORD accessMode = PIPE_ACCESS_DUPLEX; + DWORD pipeType = PIPE_TYPE_BYTE; + DWORD defaultTimeout = 0; + DWORD maxInstances = PIPE_UNLIMITED_INSTANCES; + uint32_t instanceCount = 0; + 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) { + std::lock_guard lk(mutex); + instances.push_back(inst); + } + + void unregisterInstance(class NamedPipeInstance *inst) { + std::lock_guard lk(mutex); + auto it = std::find(instances.begin(), instances.end(), inst); + if (it != instances.end()) { + instances.erase(it); + } + } + + bool reserveInstance(DWORD access, DWORD type, DWORD timeout, DWORD maxAllowed, bool firstFlag, bool isNew, + DWORD &error) { + error = ERROR_SUCCESS; + std::lock_guard lk(mutex); + if (isNew) { + accessMode = access; + pipeType = type; + defaultTimeout = timeout; + maxInstances = maxAllowed; + } else { + if (accessMode != access || pipeType != type || defaultTimeout != timeout) { + error = ERROR_ACCESS_DENIED; + return false; + } + if (maxInstances != maxAllowed) { + error = ERROR_ACCESS_DENIED; + return false; + } + } + if (firstFlag && instanceCount > 0) { + error = ERROR_ACCESS_DENIED; + return false; + } + if (maxInstances != PIPE_UNLIMITED_INSTANCES && instanceCount >= maxInstances) { + error = ERROR_PIPE_BUSY; + return false; + } + ++instanceCount; + return true; + } + + bool releaseInstance() { + std::lock_guard lk(mutex); + if (instanceCount > 0) { + --instanceCount; + } + return instanceCount == 0; + } +}; + +struct NamedPipeInstance final : FileObject { + Pin state; + int companionFd = -1; + DWORD accessMode; + DWORD pipeMode; + bool clientConnected = false; + + NamedPipeInstance(int fd, Pin st, int companion, DWORD open, DWORD mode) + : FileObject(fd), state(std::move(st)), companionFd(companion), accessMode(open), pipeMode(mode) { + if (state) { + state->registerInstance(this); + } + } + + ~NamedPipeInstance() override { + if (companionFd >= 0) { + close(companionFd); + companionFd = -1; + } + if (state) { + state->unregisterInstance(this); + } + if (state) { + state->releaseInstance(); + } + } + + bool canAcceptClient(DWORD desiredAccess) const { + if (companionFd < 0 || clientConnected) { + return false; + } + DWORD access = accessMode & PIPE_ACCESS_DUPLEX; + switch (access) { + case PIPE_ACCESS_DUPLEX: + return (desiredAccess & (GENERIC_READ | GENERIC_WRITE)) != 0; + case PIPE_ACCESS_INBOUND: + return (desiredAccess & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0; + case PIPE_ACCESS_OUTBOUND: + return (desiredAccess & (GENERIC_READ | FILE_READ_DATA)) != 0; + default: + return false; + } + } + + int takeCompanion() { + if (companionFd < 0) { + return -1; + } + int fd = companionFd; + companionFd = -1; + clientConnected = true; + return fd; + } + + void restoreCompanion(int fd) { + companionFd = fd; + if (fd >= 0) { + clientConnected = false; + } + } +}; + +Pin acquireConnectableInstance(Pin &state, DWORD desiredAccess, DWORD &error) { + if (!state) { + error = ERROR_FILE_NOT_FOUND; + return {}; + } + std::lock_guard lk(state->mutex); + for (auto *inst : state->instances) { + if (!inst) { + continue; + } + if (inst->canAcceptClient(desiredAccess)) { + return Pin::acquire(inst); + } + } + error = ERROR_PIPE_BUSY; + return {}; +} + } // namespace +bool tryCreateFileNamedPipeA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE &outHandle) { + (void)dwShareMode; + (void)dwCreationDisposition; + + DWORD parseError = ERROR_SUCCESS; + auto parsed = parsePipeName(lpFileName, parseError); + if (!parsed) { + if (parseError != ERROR_SUCCESS) { + wibo::lastError = parseError; + outHandle = INVALID_HANDLE_VALUE; + return true; + } + return false; + } + + auto state = wibo::g_namespace.getAs(parsed->namespaceKey); + if (!state) { + wibo::lastError = ERROR_FILE_NOT_FOUND; + outHandle = INVALID_HANDLE_VALUE; + return true; + } + + DWORD acquireError = ERROR_SUCCESS; + auto instancePin = acquireConnectableInstance(state, dwDesiredAccess, acquireError); + if (!instancePin) { + wibo::lastError = acquireError; + outHandle = INVALID_HANDLE_VALUE; + return true; + } + + int clientFd = instancePin->takeCompanion(); + if (clientFd < 0) { + wibo::lastError = ERROR_PIPE_BUSY; + outHandle = INVALID_HANDLE_VALUE; + return true; + } + + bool inherit = lpSecurityAttributes && lpSecurityAttributes->bInheritHandle; + configureInheritability(clientFd, inherit); + + auto clientObj = make_pin(clientFd); + if (!clientObj) { + instancePin->restoreCompanion(clientFd); + wibo::lastError = ERROR_NOT_ENOUGH_MEMORY; + outHandle = INVALID_HANDLE_VALUE; + return true; + } + clientFd = -1; + + clientObj->shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE; + clientObj->overlapped = (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED) != 0; + + uint32_t grantedAccess = SYNCHRONIZE; + switch (instancePin->accessMode & PIPE_ACCESS_DUPLEX) { + case PIPE_ACCESS_DUPLEX: + grantedAccess |= FILE_GENERIC_READ | FILE_GENERIC_WRITE; + break; + case PIPE_ACCESS_INBOUND: + grantedAccess |= FILE_GENERIC_WRITE; + break; + case PIPE_ACCESS_OUTBOUND: + grantedAccess |= FILE_GENERIC_READ; + break; + default: + break; + } + + uint32_t handleFlags = inherit ? HANDLE_FLAG_INHERIT : 0; + outHandle = wibo::handles().alloc(std::move(clientObj), grantedAccess, handleFlags); + return true; +} + BOOL WIN_FUNC CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize) { HOST_CONTEXT_GUARD(); DEBUG_LOG("CreatePipe(%p, %p, %p, %u)\n", hReadPipe, hWritePipe, lpPipeAttributes, nSize); @@ -66,4 +359,147 @@ BOOL WIN_FUNC CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRI return TRUE; } +HANDLE WIN_FUNC CreateNamedPipeA(LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, + DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("CreateNamedPipeA(%s, 0x%08x, 0x%08x, %u, %u, %u, %u, %p)\n", lpName ? lpName : "(null)", dwOpenMode, + dwPipeMode, nMaxInstances, nOutBufferSize, nInBufferSize, nDefaultTimeOut, lpSecurityAttributes); + + DWORD parseError = ERROR_SUCCESS; + std::optional parsed = parsePipeName(lpName, parseError); + if (!parsed) { + wibo::lastError = (parseError == ERROR_SUCCESS) ? ERROR_INVALID_NAME : parseError; + return INVALID_HANDLE_VALUE; + } + + constexpr DWORD kAllowedOpenFlags = PIPE_ACCESS_DUPLEX | WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY | + FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED; + if ((dwOpenMode & ~kAllowedOpenFlags) != 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return INVALID_HANDLE_VALUE; + } + + DWORD accessMode = dwOpenMode & PIPE_ACCESS_DUPLEX; + if (accessMode != PIPE_ACCESS_DUPLEX && accessMode != PIPE_ACCESS_INBOUND && accessMode != PIPE_ACCESS_OUTBOUND) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return INVALID_HANDLE_VALUE; + } + + const bool firstInstanceFlag = (dwOpenMode & FILE_FLAG_FIRST_PIPE_INSTANCE) != 0; + const bool inheritHandles = lpSecurityAttributes && lpSecurityAttributes->bInheritHandle; + const bool overlapped = (dwOpenMode & FILE_FLAG_OVERLAPPED) != 0; + + constexpr DWORD kAllowedPipeModeFlags = + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS; + if ((dwPipeMode & ~kAllowedPipeModeFlags) != 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return INVALID_HANDLE_VALUE; + } + if ((dwPipeMode & PIPE_READMODE_MESSAGE) != 0 && (dwPipeMode & PIPE_TYPE_MESSAGE) == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return INVALID_HANDLE_VALUE; + } + + DWORD pipeType = (dwPipeMode & PIPE_TYPE_MESSAGE) != 0 ? PIPE_TYPE_MESSAGE : PIPE_TYPE_BYTE; + DWORD normalizedMaxInstances = normalizeMaxInstances(nMaxInstances); + + auto [state, isNewState] = wibo::g_namespace.getOrCreate( + parsed->namespaceKey, [&]() -> NamedPipeState * { return new NamedPipeState(parsed->key); }); + if (!state) { + wibo::lastError = ERROR_NOT_ENOUGH_MEMORY; + return INVALID_HANDLE_VALUE; + } + + bool instanceReserved = false; + DWORD reserveError = ERROR_SUCCESS; + if (!state->reserveInstance(accessMode, pipeType, nDefaultTimeOut, normalizedMaxInstances, firstInstanceFlag, + isNewState, reserveError)) { + wibo::lastError = reserveError; + return INVALID_HANDLE_VALUE; + } + instanceReserved = true; + + int serverFd = -1; + int companionFd = -1; + auto fail = [&](DWORD err) -> HANDLE { + if (serverFd >= 0) { + close(serverFd); + serverFd = -1; + } + if (companionFd >= 0) { + close(companionFd); + companionFd = -1; + } + if (instanceReserved && state) { + state->releaseInstance(); + instanceReserved = false; + } + wibo::lastError = err; + return INVALID_HANDLE_VALUE; + }; + + int fds[2] = {-1, -1}; + if (accessMode == PIPE_ACCESS_DUPLEX) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) { + int savedErrno = errno; + return fail(wibo::winErrorFromErrno(savedErrno)); + } + serverFd = fds[0]; + companionFd = fds[1]; + } else { + if (pipe(fds) != 0) { + int savedErrno = errno; + return fail(wibo::winErrorFromErrno(savedErrno)); + } + if (accessMode == PIPE_ACCESS_INBOUND) { + serverFd = fds[0]; + companionFd = fds[1]; + if (nInBufferSize != 0) { + fcntl(serverFd, F_SETPIPE_SZ, static_cast(nInBufferSize)); + } + } else { + serverFd = fds[1]; + companionFd = fds[0]; + if (nOutBufferSize != 0) { + fcntl(serverFd, F_SETPIPE_SZ, static_cast(nOutBufferSize)); + } + } + } + + configureInheritability(serverFd, inheritHandles); + if (companionFd >= 0) { + configureInheritability(companionFd, inheritHandles); + } + + auto pipeObj = make_pin(serverFd, state.clone(), companionFd, accessMode, dwPipeMode); + if (!pipeObj) { + return fail(ERROR_NOT_ENOUGH_MEMORY); + } + serverFd = -1; + companionFd = -1; + instanceReserved = false; + + pipeObj->shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE; + pipeObj->overlapped = overlapped; + + uint32_t grantedAccess = SYNCHRONIZE; + switch (accessMode) { + case PIPE_ACCESS_DUPLEX: + grantedAccess |= FILE_GENERIC_READ | FILE_GENERIC_WRITE; + break; + case PIPE_ACCESS_INBOUND: + grantedAccess |= FILE_GENERIC_READ; + break; + case PIPE_ACCESS_OUTBOUND: + grantedAccess |= FILE_GENERIC_WRITE; + break; + default: + break; + } + + uint32_t handleFlags = inheritHandles ? HANDLE_FLAG_INHERIT : 0; + return wibo::handles().alloc(std::move(pipeObj), grantedAccess, handleFlags); +} + } // namespace kernel32 diff --git a/dll/kernel32/namedpipeapi.h b/dll/kernel32/namedpipeapi.h index 3ce1bae..c5baf34 100644 --- a/dll/kernel32/namedpipeapi.h +++ b/dll/kernel32/namedpipeapi.h @@ -6,5 +6,11 @@ namespace kernel32 { BOOL WIN_FUNC CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize); +HANDLE WIN_FUNC CreateNamedPipeA(LPCSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, + DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes); +bool tryCreateFileNamedPipeA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE &outHandle); } // namespace kernel32 diff --git a/src/common.h b/src/common.h index 480cbb7..e3075fd 100644 --- a/src/common.h +++ b/src/common.h @@ -111,8 +111,10 @@ constexpr DWORD STILL_ACTIVE = 259; constexpr DWORD FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; constexpr DWORD FILE_FLAG_DELETE_ON_CLOSE = 0x04000000; +constexpr DWORD FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; constexpr DWORD FILE_FLAG_NO_BUFFERING = 0x20000000; constexpr DWORD FILE_FLAG_OVERLAPPED = 0x40000000; +constexpr DWORD FILE_FLAG_WRITE_THROUGH = 0x80000000; constexpr DWORD STD_INPUT_HANDLE = ((DWORD)-10); constexpr DWORD STD_OUTPUT_HANDLE = ((DWORD)-11); @@ -135,6 +137,9 @@ constexpr DWORD FILE_WRITE_ATTRIBUTES = 0x00000100; constexpr DWORD SYNCHRONIZE = 0x00100000; constexpr DWORD DELETE = 0x00010000; +constexpr DWORD WRITE_DAC = 0x00040000; +constexpr DWORD WRITE_OWNER = 0x00080000; +constexpr DWORD ACCESS_SYSTEM_SECURITY = 0x01000000; constexpr DWORD STANDARD_RIGHTS_READ = 0x00020000; constexpr DWORD STANDARD_RIGHTS_WRITE = 0x00020000; @@ -201,6 +206,20 @@ constexpr DWORD FILE_SHARE_READ = 0x00000001; constexpr DWORD FILE_SHARE_WRITE = 0x00000002; constexpr DWORD FILE_SHARE_DELETE = 0x00000004; +constexpr DWORD PIPE_ACCESS_INBOUND = 0x00000001; +constexpr DWORD PIPE_ACCESS_OUTBOUND = 0x00000002; +constexpr DWORD PIPE_ACCESS_DUPLEX = 0x00000003; + +constexpr DWORD PIPE_TYPE_BYTE = 0x00000000; +constexpr DWORD PIPE_TYPE_MESSAGE = 0x00000004; +constexpr DWORD PIPE_READMODE_BYTE = 0x00000000; +constexpr DWORD PIPE_READMODE_MESSAGE = 0x00000002; +constexpr DWORD PIPE_WAIT = 0x00000000; +constexpr DWORD PIPE_NOWAIT = 0x00000001; +constexpr DWORD PIPE_ACCEPT_REMOTE_CLIENTS = 0x00000000; +constexpr DWORD PIPE_REJECT_REMOTE_CLIENTS = 0x00000008; +constexpr DWORD PIPE_UNLIMITED_INSTANCES = 255; + struct UNICODE_STRING { unsigned short Length; unsigned short MaximumLength; diff --git a/src/errors.h b/src/errors.h index 44e7da2..0f52d25 100644 --- a/src/errors.h +++ b/src/errors.h @@ -26,6 +26,7 @@ #define ERROR_IO_INCOMPLETE 996 #define ERROR_IO_PENDING 997 #define ERROR_OPERATION_ABORTED 995 +#define ERROR_PIPE_BUSY 231 #define ERROR_NONE_MAPPED 1332 #define ERROR_RESOURCE_DATA_NOT_FOUND 1812 #define ERROR_RESOURCE_TYPE_NOT_FOUND 1813 diff --git a/src/handles.h b/src/handles.h index 732d05e..42eadd8 100644 --- a/src/handles.h +++ b/src/handles.h @@ -25,6 +25,7 @@ enum class ObjectType : uint16_t { Thread, Heap, RegistryKey, + NamedPipe, }; enum ObjectFlags : uint16_t { diff --git a/src/strutil.cpp b/src/strutil.cpp index bbb1426..843498a 100644 --- a/src/strutil.cpp +++ b/src/strutil.cpp @@ -232,6 +232,15 @@ std::vector stringToWideString(const char *src, size_t length) { return res; } +std::u16string stringToUtf16(std::string_view str) { + std::u16string result; + result.reserve(str.size()); + for (unsigned char ch : str) { + result.push_back(static_cast(ch)); + } + return result; +} + long wstrtol(const uint16_t *string, uint16_t **end_ptr, int base) { if (!string) { if (end_ptr) diff --git a/src/strutil.h b/src/strutil.h index bc90bb9..6b8e4de 100644 --- a/src/strutil.h +++ b/src/strutil.h @@ -17,6 +17,7 @@ uint16_t *wstrcpy(uint16_t *dest, const uint16_t *src); size_t wstrncpy(uint16_t *dst, const uint16_t *src, size_t n); std::string wideStringToString(const uint16_t *src, int len = -1); std::vector stringToWideString(const char *src, size_t length = static_cast(-1)); +std::u16string stringToUtf16(std::string_view str); long wstrtol(const uint16_t *string, uint16_t **end_ptr, int base); unsigned long wstrtoul(const uint16_t *string, uint16_t **end_ptr, int base); void toLowerInPlace(std::string &str); diff --git a/test/test_namedpipe.c b/test/test_namedpipe.c new file mode 100644 index 0000000..60f3168 --- /dev/null +++ b/test/test_namedpipe.c @@ -0,0 +1,74 @@ +#include "test_assert.h" + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +static const char *kPipeName = "\\\\.\\pipe\\wibo_test_namedpipe"; + +static void write_checked(HANDLE handle, const char *msg) { + DWORD written = 0; + TEST_CHECK(WriteFile(handle, msg, (DWORD)strlen(msg), &written, NULL)); + TEST_CHECK_EQ((DWORD)strlen(msg), written); +} + +static void read_checked(HANDLE handle, const char *expected) { + char buffer[64] = {0}; + DWORD read = 0; + TEST_CHECK(ReadFile(handle, buffer, sizeof(buffer), &read, NULL)); + TEST_CHECK_EQ((DWORD)strlen(expected), read); + TEST_CHECK(memcmp(buffer, expected, read) == 0); +} + +int main(void) { + SetLastError(0xdeadbeefu); + HANDLE missing = CreateFileA(kPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + TEST_CHECK(missing == INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_FILE_NOT_FOUND, GetLastError()); + + HANDLE pipe = CreateNamedPipeA(kPipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, + 1024, 1024, 0, NULL); + TEST_CHECK(pipe != INVALID_HANDLE_VALUE); + + HANDLE client = + CreateFileA(kPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + TEST_CHECK(client != INVALID_HANDLE_VALUE); + + write_checked(client, "ping"); + read_checked(pipe, "ping"); + + write_checked(pipe, "pong"); + read_checked(client, "pong"); + + HANDLE invalidPrefix = CreateNamedPipeA("\\\\.\\pipe\\", PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL); + TEST_CHECK(invalidPrefix == INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError()); + + SetLastError(0xdeadbeefu); + HANDLE busy = CreateFileA(kPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + TEST_CHECK(busy == INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_PIPE_BUSY, GetLastError()); + + SetLastError(0xdeadbeefu); + HANDLE invalidName = CreateNamedPipeA("invalid", PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL); + TEST_CHECK(invalidName == INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_INVALID_NAME, GetLastError()); + + HANDLE badMode = CreateNamedPipeA(kPipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + 1, 1024, 1024, 0, NULL); + TEST_CHECK(badMode == INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); + + HANDLE nullName = CreateNamedPipeA(NULL, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, + 1024, 1024, 0, NULL); + TEST_CHECK(nullName == INVALID_HANDLE_VALUE); + TEST_CHECK_EQ(ERROR_PATH_NOT_FOUND, GetLastError()); + + TEST_CHECK(CloseHandle(client)); + TEST_CHECK(CloseHandle(pipe)); + + return 0; +}