diff --git a/CMakeLists.txt b/CMakeLists.txt index e493b63f5..3d34075c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -607,6 +607,7 @@ if(LIBUV_BUILD_TESTS) test/test-pipe-server-close.c test/test-pipe-set-fchmod.c test/test-pipe-set-non-blocking.c + test/test-pipe-win-uds.c test/test-platform-output.c test/test-poll-close-doesnt-corrupt-stack.c test/test-poll-close.c diff --git a/include/uv.h b/include/uv.h index d435a8de3..802db33b9 100644 --- a/include/uv.h +++ b/include/uv.h @@ -864,7 +864,8 @@ enum { * uv_pipe_t is a subclass of uv_stream_t. * * Representing a pipe stream or pipe server. On Windows this is a Named - * Pipe. On Unix this is a Unix domain socket. + * Pipe or a Unix domain socket depends on the init flags. On Unix this is + * a Unix domain socket. */ struct uv_pipe_s { UV_HANDLE_FIELDS @@ -873,7 +874,22 @@ struct uv_pipe_s { UV_PIPE_PRIVATE_FIELDS }; +enum uv_pipe_init_flags { + /* + * Enable IPC mode of pipe. + */ + UV_PIPE_INIT_IPC = 1u << 0, + + /* + * Use Unix Domain Socket instead of NamedPipe on Windows. Will validate + * the `name` as a file path when calling bind / connect if this flag is set. + * On non-Windows platform this flag is ignored. + */ + UV_PIPE_INIT_WIN_UDS = 1u << 1, +}; + UV_EXTERN int uv_pipe_init(uv_loop_t*, uv_pipe_t* handle, int ipc); +UV_EXTERN int uv_pipe_init_ex(uv_loop_t* loop, uv_pipe_t* handle, unsigned int flags); UV_EXTERN int uv_pipe_open(uv_pipe_t*, uv_file file); UV_EXTERN int uv_pipe_bind(uv_pipe_t* handle, const char* name); UV_EXTERN int uv_pipe_bind2(uv_pipe_t* handle, diff --git a/include/uv/win.h b/include/uv/win.h index 5e20606c9..0845a0d1a 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -376,7 +376,14 @@ typedef struct { ULONG_PTR result; /* overlapped.Internal is reused to hold the result */\ HANDLE pipeHandle; \ DWORD duplex_flags; \ - WCHAR* name; \ + /* When using unix domain socket, ConnectEx IOCP result will overwrite + * result + pipeHandle, to keep the ABI, reusing the name field to store + * the pending uds_socket for connect handler. + */ \ + union { \ + WCHAR* name; \ + SOCKET uds_socket; \ + }; \ } connect; \ } u; \ struct uv_req_s* next_req; @@ -401,6 +408,7 @@ typedef struct { UV_REQ_FIELDS \ HANDLE pipeHandle; \ struct uv_pipe_accept_s* next_pending; \ + char accept_buffer[sizeof(struct sockaddr_storage) * 2 + 32]; \ } uv_pipe_accept_t; \ \ typedef struct uv_tcp_accept_s { \ @@ -486,7 +494,14 @@ typedef struct { #define UV_PIPE_PRIVATE_FIELDS \ HANDLE handle; \ - WCHAR* name; \ + /* + * The uds doesn't use wchar name, reuse this memory to store raw utf-8 name + * while keep the ABI compact. + */ \ + union { \ + char* pathname; \ + WCHAR* name; \ + }; \ union { \ struct { uv_pipe_server_fields } serv; \ struct { uv_pipe_connection_fields } conn; \ diff --git a/src/unix/pipe.c b/src/unix/pipe.c index c9902095f..599949aa0 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -58,6 +58,17 @@ static int includes_invalid_nul(const char *s, size_t n) { int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) { + return uv_pipe_init_ex(loop, handle, ipc ? UV_PIPE_INIT_IPC : 0); +} + + +int uv_pipe_init_ex(uv_loop_t* loop, uv_pipe_t* handle, unsigned int flags) { + int ipc = (flags & UV_PIPE_INIT_IPC) != 0; + if (flags & ~(UV_PIPE_INIT_IPC | UV_PIPE_INIT_WIN_UDS)) + return UV_EINVAL; + if (flags & UV_PIPE_INIT_WIN_UDS) + return UV_EINVAL; + uv__stream_init(loop, (uv_stream_t*)handle, UV_NAMED_PIPE); handle->shutdown_req = NULL; handle->connect_req = NULL; diff --git a/src/uv-common.h b/src/uv-common.h index 05961132a..e114652e8 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -122,6 +122,7 @@ enum { /* Only used by uv_pipe_t handles. */ UV_HANDLE_NON_OVERLAPPED_PIPE = 0x01000000, UV_HANDLE_PIPESERVER = 0x02000000, + UV_HANDLE_WIN_UDS_PIPE = 0x04000000, /* Only used by uv_tty_t handles. */ UV_HANDLE_TTY_READABLE = 0x01000000, diff --git a/src/win/internal.h b/src/win/internal.h index db488be77..962222116 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -298,9 +298,6 @@ void uv__winsock_init(void); int uv__ntstatus_to_winsock_error(NTSTATUS status); -BOOL uv__get_acceptex_function(SOCKET socket, LPFN_ACCEPTEX* target); -BOOL uv__get_connectex_function(SOCKET socket, LPFN_CONNECTEX* target); - int WSAAPI uv__wsarecv_workaround(SOCKET socket, WSABUF* buffers, DWORD buffer_count, DWORD* bytes, DWORD* flags, WSAOVERLAPPED *overlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE completion_routine); @@ -320,6 +317,10 @@ extern int uv_tcp_non_ifs_lsp_ipv6; extern struct sockaddr_in uv_addr_ip4_any_; extern struct sockaddr_in6 uv_addr_ip6_any_; +/* WSA function pointers */ +extern LPFN_ACCEPTEX uv_wsa_acceptex; +extern LPFN_CONNECTEX uv_wsa_connectex; + /* * Wake all loops with fake message */ diff --git a/src/win/pipe.c b/src/win/pipe.c index 8f86a1fee..4fb9a95cc 100644 --- a/src/win/pipe.c +++ b/src/win/pipe.c @@ -35,6 +35,15 @@ #include #include +/* Runtime feature check: prefer if (UV__ENABLE_WIN_UDS_PIPE) over #ifdef + * so the compiler checks both code paths even when not enabled. */ +#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__) +#include +#define UV__ENABLE_WIN_UDS_PIPE 1 +#else +#define UV__ENABLE_WIN_UDS_PIPE 0 +#endif + /* A zero-size buffer for use by uv_pipe_read */ static char uv_zero_[] = ""; @@ -97,6 +106,47 @@ static void eof_timer_cb(uv_timer_t* timer); static void eof_timer_destroy(uv_pipe_t* pipe); static void eof_timer_close_cb(uv_handle_t* handle); +/* + * When use with bind, the file path must not exist for successful creation + * of server socket. When use with connect, the file path must exist so that + * the client socket can connect to. + * The UDS on Windows only supports this pathname mode. + */ +static int uv__win_uds_pipe_file_exists(const char* path, int* exists) { + int err; + WCHAR* wpath; + DWORD attrib; + + if (exists == NULL || path == NULL) { + return UV_EINVAL; + } + + err = uv__convert_utf8_to_utf16(path, &wpath); + if (err) { + return err; + } + + attrib = GetFileAttributesW(wpath); + *exists = (attrib != INVALID_FILE_ATTRIBUTES && + !(attrib & FILE_ATTRIBUTE_DIRECTORY)); + + uv__free(wpath); + return 0; +} + + +static void uv__close_pipe_handle(uv_pipe_t* handle, HANDLE h) { + /* Microsoft says don't close socket with CloseHandle, so when we use + * unix domain socket we should use closesocket instead of CloseHandle. + * https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle + */ + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + closesocket((SOCKET) h); + } else { + CloseHandle(h); + } +} + /* Does the file path contain embedded nul bytes? */ static int includes_nul(const char *s, size_t n) { @@ -112,8 +162,27 @@ static void uv__unique_pipe_name(unsigned long long ptr, char* name, size_t size int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) { - uv__stream_init(loop, (uv_stream_t*)handle, UV_NAMED_PIPE); + return uv_pipe_init_ex(loop, handle, ipc ? UV_PIPE_INIT_IPC : 0); +} + +/* + * Init pipe with flags. + */ +int uv_pipe_init_ex(uv_loop_t* loop, uv_pipe_t* handle, unsigned int flags) { + int ipc = (flags & UV_PIPE_INIT_IPC) != 0; + int uds = (flags & UV_PIPE_INIT_WIN_UDS) != 0; + + /* Reject unknown flags for forwards compatibility. */ + if (flags & ~(UV_PIPE_INIT_IPC | UV_PIPE_INIT_WIN_UDS)) + return UV_EINVAL; + + if (ipc && uds) { + /* Unix domain socket on Windows doesn't work with IPC mode currently. */ + return EINVAL; + } + + uv__stream_init(loop, (uv_stream_t*)handle, UV_NAMED_PIPE); handle->reqs_pending = 0; handle->handle = INVALID_HANDLE_VALUE; handle->name = NULL; @@ -124,6 +193,10 @@ int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) { handle->ipc = ipc; handle->pipe.conn.non_overlapped_writes_tail = NULL; + if (uds) { + handle->flags |= UV_HANDLE_WIN_UDS_PIPE; + } + return 0; } @@ -197,7 +270,7 @@ static HANDLE open_named_pipe(const WCHAR* name, DWORD* duplex_flags) { static void close_pipe(uv_pipe_t* pipe) { assert(pipe->u.fd == -1 || pipe->u.fd > 2); if (pipe->u.fd == -1) - CloseHandle(pipe->handle); + uv__close_pipe_handle(pipe, pipe->handle); else _close(pipe->u.fd); @@ -205,7 +278,7 @@ static void close_pipe(uv_pipe_t* pipe) { pipe->handle = INVALID_HANDLE_VALUE; } - +/* Pipe pair use named pipe only, no uds support. */ static int uv__pipe_server( HANDLE* pipeHandle_ptr, DWORD access, char* name, size_t nameSize, unsigned long long random) { @@ -246,6 +319,7 @@ static int uv__pipe_server( } +/* Pipe pair use named pipe only, no uds support. */ static int uv__create_pipe_pair( HANDLE* server_pipe_ptr, HANDLE* client_pipe_ptr, unsigned int server_flags, unsigned int client_flags, @@ -346,6 +420,7 @@ static int uv__create_pipe_pair( } +/* Pipe pair use named pipe only, no uds support. */ int uv_pipe(uv_file fds[2], int read_flags, int write_flags) { uv_file temp[2]; int err; @@ -391,6 +466,7 @@ int uv_pipe(uv_file fds[2], int read_flags, int write_flags) { } +/* Pipe pair use named pipe only, no uds support. */ int uv__create_stdio_pipe_pair(uv_loop_t* loop, uv_pipe_t* parent_pipe, HANDLE* child_pipe_ptr, unsigned int flags) { /* The parent_pipe is always the server_pipe and kept by libuv. @@ -479,6 +555,13 @@ static int uv__set_pipe_handle(uv_loop_t* loop, if (handle->handle != INVALID_HANDLE_VALUE) return UV_EBUSY; + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* Skip if the handle is a unix domain socket. + * We already created IOCP at connect2. + */ + goto uds_pipe; + } + if (!SetNamedPipeHandleState(pipeHandle, &mode, NULL, NULL)) { err = GetLastError(); if (err == ERROR_ACCESS_DENIED) { @@ -530,6 +613,7 @@ static int uv__set_pipe_handle(uv_loop_t* loop, } } +uds_pipe: handle->handle = pipeHandle; handle->u.fd = fd; handle->flags |= duplex_flags; @@ -538,7 +622,62 @@ static int uv__set_pipe_handle(uv_loop_t* loop, } -static int pipe_alloc_accept(uv_loop_t* loop, uv_pipe_t* handle, +#if UV__ENABLE_WIN_UDS_PIPE +static int pipe_alloc_accept_unix_domain_socket(uv_loop_t* loop, uv_pipe_t* handle, + uv_pipe_accept_t* req, const char* name, BOOL firstInstance) { + int ret = 0, err = 0; + SOCKET accept_fd; + SOCKET server_fd; + struct sockaddr_un addr = {0}; + + assert(req->pipeHandle == INVALID_HANDLE_VALUE); + + /* Create a non bound socket for AcceptEx second parameter. */ + accept_fd = socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP); + if (accept_fd == INVALID_SOCKET) { + return WSAGetLastError(); + } + req->pipeHandle = (HANDLE) accept_fd; + + if (firstInstance) { + /* First instance, only possible at bind, create the server socket. */ + server_fd = socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP); + if (server_fd == INVALID_SOCKET) { + return WSAGetLastError(); + } + + addr.sun_family = AF_UNIX; + ret = uv__strscpy(addr.sun_path, name, sizeof(addr.sun_path)); + if (ret < 0) { + return ret; + } + + ret = bind(server_fd, (const struct sockaddr*)&addr, sizeof(addr)); + if (ret == SOCKET_ERROR) { + err = WSAGetLastError(); + closesocket(server_fd); + return err; + } + + /* Associate it with IOCP so we can get events. */ + if (CreateIoCompletionPort((HANDLE) server_fd, + loop->iocp, + (ULONG_PTR) handle, + 0) == NULL) { + closesocket(server_fd); + return GetLastError(); + } + + /* First instance, save for AcceptEx first parameter. */ + handle->handle = (HANDLE) server_fd; + } + + return 0; +} +#endif + + +static int pipe_alloc_accept_named_pipe(uv_loop_t* loop, uv_pipe_t* handle, uv_pipe_accept_t* req, BOOL firstInstance) { assert(req->pipeHandle == INVALID_HANDLE_VALUE); @@ -607,6 +746,12 @@ void uv__pipe_shutdown(uv_loop_t* loop, uv_pipe_t* handle, uv_shutdown_t *req) { return; } + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* Unix domain socket support neither query 'named pipe' info, nor FlushFileBuffer. */ + uv__insert_pending_req(loop, (uv_req_t*) req); + return; + } + /* Try to avoid flushing the pipe buffer in the thread pool. */ nt_status = pNtQueryInformationFile(handle->handle, &io_status, @@ -695,6 +840,11 @@ void uv__pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) { void uv_pipe_pending_instances(uv_pipe_t* handle, int count) { if (handle->flags & UV_HANDLE_BOUND) return; + + /* Unix domain socket only use 1 pending instance. */ + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) + return; + handle->pipe.serv.pending_instances = count; handle->flags |= UV_HANDLE_PIPESERVER; } @@ -711,9 +861,12 @@ int uv_pipe_bind2(uv_pipe_t* handle, size_t namelen, unsigned int flags) { uv_loop_t* loop = handle->loop; - int i, err; + int i, err = 0; uv_pipe_accept_t* req; char* name_copy; + int use_uds_pipe; + int uds_file_exists; + int uds_err; if (flags & ~UV_PIPE_NO_TRUNCATE) { return UV_EINVAL; @@ -731,6 +884,22 @@ int uv_pipe_bind2(uv_pipe_t* handle, return UV_EINVAL; } + use_uds_pipe = (handle->flags & UV_HANDLE_WIN_UDS_PIPE) != 0; + +#if UV__ENABLE_WIN_UDS_PIPE + /* Since UDS on Windows is a new feature, always reject too-long paths + * (no truncation for backwards compatibility needed). */ + if (use_uds_pipe) { + if (namelen >= sizeof(((struct sockaddr_un*) 0)->sun_path)) + return UV_EINVAL; + } +#else + if (use_uds_pipe) { + return UV_ENOSYS; + } +#endif + + /* Already bound? */ if (handle->flags & UV_HANDLE_BOUND) { return UV_EINVAL; } @@ -751,10 +920,17 @@ int uv_pipe_bind2(uv_pipe_t* handle, handle->pipe.serv.pending_instances = default_pending_pipe_instances; } - err = UV_ENOMEM; + if (use_uds_pipe) { + /* Only use 1 pending instance when use unix domain socket, cause + * call AcceptEx multiple times seems result in multiple accept events. + * Not the expected queue behavior, that only one of them is triggered. */ + handle->pipe.serv.pending_instances = 1; + } + handle->pipe.serv.accept_reqs = (uv_pipe_accept_t*) uv__malloc(sizeof(uv_pipe_accept_t) * handle->pipe.serv.pending_instances); if (handle->pipe.serv.accept_reqs == NULL) { + err = UV_ENOMEM; goto error; } @@ -766,23 +942,61 @@ int uv_pipe_bind2(uv_pipe_t* handle, req->next_pending = NULL; } - /* TODO(bnoordhuis) Add converters that take a |length| parameter. */ - err = uv__convert_utf8_to_utf16(name_copy, &handle->name); - uv__free(name_copy); - name_copy = NULL; + if (use_uds_pipe) { + /* Use unix domain socket we save the original path name copy. */ + handle->pathname = name_copy; + name_copy = NULL; + } else { + /* TODO(bnoordhuis) Add converters that take a |length| parameter. */ + err = uv__convert_utf8_to_utf16(name_copy, &handle->name); + uv__free(name_copy); + name_copy = NULL; + } if (err) { goto error; } +#if UV__ENABLE_WIN_UDS_PIPE + if (use_uds_pipe) { + err = uv__win_uds_pipe_file_exists(handle->pathname, &uds_file_exists); + if (err) { + goto error; + } + + /* The uds file must not exist before bind. */ + if (uds_file_exists) { + err = UV_EEXIST; + goto error; + } + + uds_err = pipe_alloc_accept_unix_domain_socket( + loop, handle, &handle->pipe.serv.accept_reqs[0], handle->pathname, TRUE); + if (uds_err) { + if (uds_err == WSAENETDOWN) { + /* + * Typically it means the file at pathname cannot be created, + * possibly bad parent directory in path. + */ + err = UV_EINVAL; + } else { + err = uv_translate_sys_error(uds_err); + } + goto error; + } + + goto uds_pipe; + } +#endif + /* * Attempt to create the first pipe with FILE_FLAG_FIRST_PIPE_INSTANCE. * If this fails then there's already a pipe server for the given pipe name. */ - if (!pipe_alloc_accept(loop, - handle, - &handle->pipe.serv.accept_reqs[0], - TRUE)) { + if (!pipe_alloc_accept_named_pipe(loop, + handle, + &handle->pipe.serv.accept_reqs[0], + TRUE)) { err = GetLastError(); if (err == ERROR_ACCESS_DENIED) { err = UV_EADDRINUSE; @@ -794,6 +1008,7 @@ int uv_pipe_bind2(uv_pipe_t* handle, goto error; } +uds_pipe: handle->pipe.serv.pending_accepts = NULL; handle->flags |= UV_HANDLE_PIPESERVER; handle->flags |= UV_HANDLE_BOUND; @@ -880,11 +1095,21 @@ int uv_pipe_connect2(uv_connect_t* req, unsigned int flags, uv_connect_cb cb) { uv_loop_t* loop; - int err; + int err = 0; size_t nameSize; HANDLE pipeHandle = INVALID_HANDLE_VALUE; DWORD duplex_flags; char* name_copy; + DWORD bytes; + + int use_uds_pipe; + +#if UV__ENABLE_WIN_UDS_PIPE + int uds_file_exists; + SOCKET uds_client_fd; + struct sockaddr_un uds_addr_bind = {0}; + struct sockaddr_un uds_addr_real = {0}; +#endif loop = handle->loop; UV_REQ_INIT(req, UV_CONNECT); @@ -910,6 +1135,21 @@ int uv_pipe_connect2(uv_connect_t* req, return UV_EINVAL; } + use_uds_pipe = (handle->flags & UV_HANDLE_WIN_UDS_PIPE) != 0; + +#if UV__ENABLE_WIN_UDS_PIPE + /* Since UDS on Windows is a new feature, always reject too-long paths + * (no truncation for backwards compatibility needed). */ + if (use_uds_pipe) { + if (namelen >= sizeof(((struct sockaddr_un*) 0)->sun_path)) + return UV_EINVAL; + } +#else + if (use_uds_pipe) { + return UV_ENOSYS; + } +#endif + name_copy = uv__malloc(namelen + 1); if (name_copy == NULL) { return UV_ENOMEM; @@ -928,16 +1168,101 @@ int uv_pipe_connect2(uv_connect_t* req, } uv__pipe_connection_init(handle); - /* TODO(bnoordhuis) Add converters that take a |length| parameter. */ - err = uv__convert_utf8_to_utf16(name_copy, &handle->name); - uv__free(name_copy); - name_copy = NULL; + if (use_uds_pipe) { + /* Use unix domain socket we save the original path name copy. */ + handle->pathname = name_copy; + name_copy = NULL; + } else { + /* TODO(bnoordhuis) Add converters that take a |length| parameter. */ + err = uv__convert_utf8_to_utf16(name_copy, &handle->name); + uv__free(name_copy); + name_copy = NULL; + } if (err) { err = ERROR_NO_UNICODE_TRANSLATION; goto error; } +#if UV__ENABLE_WIN_UDS_PIPE + if (use_uds_pipe) { + err = uv__win_uds_pipe_file_exists(handle->pathname, &uds_file_exists); + if (err) { + goto error; + } + + /* The uds file must have been created by server. */ + if (!uds_file_exists) { + err = UV_ENOENT; + goto error; + } + + uds_client_fd = socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP); + if (uds_client_fd == INVALID_SOCKET) { + err = WSAGetLastError(); + goto error; + } + + uds_addr_bind.sun_family = AF_UNIX; + + /* ConnectEx need to be initially bound */ + int ret = bind(uds_client_fd, + (const struct sockaddr*) &uds_addr_bind, + sizeof(uds_addr_bind)); + if (ret != 0) { + err = WSAGetLastError(); + closesocket(uds_client_fd); + goto error; + } + + /* Associate it with IOCP so we can get events. */ + if (CreateIoCompletionPort((HANDLE) uds_client_fd, + loop->iocp, + (ULONG_PTR) handle, + 0) == NULL) { + err = GetLastError(); + closesocket(uds_client_fd); + goto error; + } + + /* Set overlapped to empty */ + memset(&req->u.io.overlapped, 0, sizeof(req->u.io.overlapped)); + + uds_addr_real.sun_family = AF_UNIX; + memcpy(uds_addr_real.sun_path, name, namelen); + uds_addr_real.sun_path[namelen] = '\0'; + + ret = uv_wsa_connectex(uds_client_fd, + (const struct sockaddr*) &uds_addr_real, + sizeof(uds_addr_real), + NULL, + 0, + &bytes, + &req->u.io.overlapped); + + if (!ret) { + err = WSAGetLastError(); + if (err != ERROR_IO_PENDING) { + closesocket(uds_client_fd); + goto error; + } + } + + /* Since we use IOCP, we can't set value to u.connect.pipeHandle + * as it will be rewritten by the result of IOCP. Thus, we set the socket + * to uds_socket (reuse the `name`) and set it to pipeHandle later at req + * handler. + */ + req->u.connect.uds_socket = uds_client_fd; + req->u.connect.duplex_flags = UV_HANDLE_WRITABLE | UV_HANDLE_READABLE; + + /* The req will be processed with IOCP. */ + handle->reqs_pending++; + REGISTER_HANDLE_REQ(loop, handle); + return 0; + } +#endif + pipeHandle = open_named_pipe(handle->name, &duplex_flags); if (pipeHandle == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_PIPE_BUSY) { @@ -1056,6 +1381,7 @@ void uv__pipe_read_stop(uv_pipe_t* handle) { void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { int i; HANDLE pipeHandle; + struct linger uds_linger = {1, 0}; if (handle->flags & UV_HANDLE_READING) { handle->flags &= ~UV_HANDLE_READING; @@ -1078,11 +1404,20 @@ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { handle->name = NULL; } + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* Force the subsequent closesocket to be abortative. */ + setsockopt((SOCKET)handle->handle, + SOL_SOCKET, + SO_LINGER, + (const char*)&uds_linger, + sizeof(uds_linger)); + } + if (handle->flags & UV_HANDLE_PIPESERVER) { for (i = 0; i < handle->pipe.serv.pending_instances; i++) { pipeHandle = handle->pipe.serv.accept_reqs[i].pipeHandle; if (pipeHandle != INVALID_HANDLE_VALUE) { - CloseHandle(pipeHandle); + uv__close_pipe_handle(handle, pipeHandle); handle->pipe.serv.accept_reqs[i].pipeHandle = INVALID_HANDLE_VALUE; } } @@ -1106,33 +1441,81 @@ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { static void uv__pipe_queue_accept(uv_loop_t* loop, uv_pipe_t* handle, uv_pipe_accept_t* req, BOOL firstInstance) { + int uds_err; + int wsa_err; + DWORD bytes_received; + assert(handle->flags & UV_HANDLE_LISTENING); - if (!firstInstance && !pipe_alloc_accept(loop, handle, req, FALSE)) { - SET_REQ_ERROR(req, GetLastError()); - uv__insert_pending_req(loop, (uv_req_t*) req); - handle->reqs_pending++; - return; + if (!firstInstance) { +#if UV__ENABLE_WIN_UDS_PIPE + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + uds_err = pipe_alloc_accept_unix_domain_socket( + loop, handle, req, handle->pathname, FALSE); + if (uds_err) { + SET_REQ_ERROR(req, uds_err); + uv__insert_pending_req(loop, (uv_req_t*) req); + handle->reqs_pending++; + return; + } + + goto uds_pipe; + } +#endif + + if (!pipe_alloc_accept_named_pipe(loop, handle, req, FALSE)) { + SET_REQ_ERROR(req, GetLastError()); + uv__insert_pending_req(loop, (uv_req_t*) req); + handle->reqs_pending++; + return; + } } +uds_pipe: assert(req->pipeHandle != INVALID_HANDLE_VALUE); /* Prepare the overlapped structure. */ memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); - if (!ConnectNamedPipe(req->pipeHandle, &req->u.io.overlapped) && - GetLastError() != ERROR_IO_PENDING) { - if (GetLastError() == ERROR_PIPE_CONNECTED) { - SET_REQ_SUCCESS(req); - } else { - CloseHandle(req->pipeHandle); - req->pipeHandle = INVALID_HANDLE_VALUE; - /* Make this req pending reporting an error. */ - SET_REQ_ERROR(req, GetLastError()); + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* AcceptEx requires dwLocalAddressLength and dwRemoteAddressLength to be + * at least 16 bytes more than the maximum address length for the transport + * protocol in use. See MSDN AcceptEx documentation. + * https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex */ + if (!uv_wsa_acceptex((SOCKET)handle->handle, + (SOCKET)req->pipeHandle, + req->accept_buffer, + 0, + sizeof(SOCKADDR_STORAGE) + 16, + sizeof(SOCKADDR_STORAGE) + 16, + &bytes_received, + &req->u.io.overlapped)) { + wsa_err = WSAGetLastError(); + if (wsa_err != ERROR_IO_PENDING) { + closesocket((SOCKET) req->pipeHandle); + req->pipeHandle = INVALID_HANDLE_VALUE; + + SET_REQ_ERROR(req, wsa_err); + uv__insert_pending_req(loop, (uv_req_t*) req); + handle->reqs_pending++; + return; + } + } + } else { + if (!ConnectNamedPipe(req->pipeHandle, &req->u.io.overlapped) && + GetLastError() != ERROR_IO_PENDING) { + if (GetLastError() == ERROR_PIPE_CONNECTED) { + SET_REQ_SUCCESS(req); + } else { + CloseHandle(req->pipeHandle); + req->pipeHandle = INVALID_HANDLE_VALUE; + /* Make this req pending reporting an error. */ + SET_REQ_ERROR(req, GetLastError()); + } + uv__insert_pending_req(loop, (uv_req_t*) req); + handle->reqs_pending++; + return; } - uv__insert_pending_req(loop, (uv_req_t*) req); - handle->reqs_pending++; - return; } /* Wait for completion via IOCP */ @@ -1184,12 +1567,32 @@ int uv__pipe_accept(uv_pipe_t* server, uv_stream_t* client) { pipe_client->handle = req->pipeHandle; pipe_client->flags |= UV_HANDLE_READABLE | UV_HANDLE_WRITABLE; + /* A unix domain socket server */ + if (server->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* Associate it with the I/O completion port. Use uv_handle_t pointer as + * completion key. */ + if (CreateIoCompletionPort(req->pipeHandle, + pipe_client->loop->iocp, + (ULONG_PTR) req->pipeHandle, + 0) == NULL) { + return GetLastError(); + } + + /* AcceptEx() implicitly binds the accepted socket. */ + pipe_client->flags |= UV_HANDLE_BOUND; + pipe_client->flags |= UV_HANDLE_WIN_UDS_PIPE; + } + /* Prepare the req to pick up a new connection */ server->pipe.serv.pending_accepts = req->next_pending; req->next_pending = NULL; req->pipeHandle = INVALID_HANDLE_VALUE; - server->handle = INVALID_HANDLE_VALUE; + if (!(server->flags & UV_HANDLE_WIN_UDS_PIPE)) { + /* Unix domain socket doesn't transfer to client ownership, so do not reset here.*/ + server->handle = INVALID_HANDLE_VALUE; + } + if (!(server->flags & UV_HANDLE_CLOSING)) { uv__pipe_queue_accept(loop, server, req, FALSE); } @@ -1203,6 +1606,7 @@ int uv__pipe_accept(uv_pipe_t* server, uv_stream_t* client) { int uv__pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) { uv_loop_t* loop = handle->loop; int i; + int err; if (handle->flags & UV_HANDLE_LISTENING) { handle->stream.serv.connection_cb = cb; @@ -1224,6 +1628,13 @@ int uv__pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) { return WSAEINVAL; } + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + err = listen((SOCKET) handle->handle, backlog); + if (err) { + return err; + } + } + handle->flags |= UV_HANDLE_LISTENING; INCREASE_ACTIVE_COUNT(loop, handle); handle->stream.serv.connection_cb = cb; @@ -2137,14 +2548,31 @@ void uv__process_pipe_read_req(uv_loop_t* loop, return; if (!REQ_SUCCESS(req)) { - /* An error occurred doing the zero-read. */ - err = GET_REQ_ERROR(req); + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + err = GET_REQ_SOCK_ERROR(req); + if (err == WSAECONNABORTED) { + /* Turn WSAECONNABORTED into UV_ECONNRESET to be consistent with Unix. + */ + err = WSAECONNRESET; + } + handle->flags &= ~(UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); - /* If the read was cancelled by uv__pipe_interrupt_read(), the request may - * indicate an ERROR_OPERATION_ABORTED error. This error isn't relevant to - * the user; we'll start a new zero-read at the end of this function. */ - if (err != ERROR_OPERATION_ABORTED) - uv__pipe_read_error_or_eof(loop, handle, err, uv_null_buf_); + if (err == WSAECONNRESET) { + uv__pipe_read_eof(loop, handle, uv_null_buf_); + } else { + uv__pipe_read_error(loop, handle, err, uv_null_buf_); + } + + } else { + /* An error occurred doing the zero-read. */ + err = GET_REQ_ERROR(req); + + /* If the read was cancelled by uv__pipe_interrupt_read(), the request may + * indicate an ERROR_OPERATION_ABORTED error. This error isn't relevant to + * the user; we'll start a new zero-read at the end of this function. */ + if (err != ERROR_OPERATION_ABORTED) + uv__pipe_read_error_or_eof(loop, handle, err, uv_null_buf_); + } } else { /* The zero-read completed without error, indicating there is data @@ -2237,6 +2665,15 @@ void uv__process_pipe_accept_req(uv_loop_t* loop, uv_pipe_t* handle, return; } + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* If it is unix domain handle, the event comes from AcceptEx IOCP. */ + setsockopt((SOCKET)req->pipeHandle, + SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, + (char*)&handle->handle, + sizeof(handle->handle)); + } + if (REQ_SUCCESS(req)) { assert(req->pipeHandle != INVALID_HANDLE_VALUE); req->next_pending = handle->pipe.serv.pending_accepts; @@ -2247,7 +2684,7 @@ void uv__process_pipe_accept_req(uv_loop_t* loop, uv_pipe_t* handle, } } else { if (req->pipeHandle != INVALID_HANDLE_VALUE) { - CloseHandle(req->pipeHandle); + uv__close_pipe_handle(handle, req->pipeHandle); req->pipeHandle = INVALID_HANDLE_VALUE; } if (!(handle->flags & UV_HANDLE_CLOSING)) { @@ -2267,6 +2704,20 @@ void uv__process_pipe_connect_req(uv_loop_t* loop, uv_pipe_t* handle, assert(handle->type == UV_NAMED_PIPE); + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* IOCP overwrites the connect.pipeHandle, so workaround here. */ + req->u.connect.pipeHandle = (HANDLE) req->u.connect.uds_socket; + + if (req->u.connect.pipeHandle) { + /* If it is unix domain handle, the event comes from ConnectEx IOCP. */ + setsockopt((SOCKET) req->u.connect.pipeHandle, + SOL_SOCKET, + SO_UPDATE_CONNECT_CONTEXT, + NULL, + 0); + } + } + UNREGISTER_HANDLE_REQ(loop, handle); err = 0; @@ -2278,7 +2729,7 @@ void uv__process_pipe_connect_req(uv_loop_t* loop, uv_pipe_t* handle, else err = uv__set_pipe_handle(loop, handle, pipeHandle, -1, duplex_flags); if (err) - CloseHandle(pipeHandle); + uv__close_pipe_handle(handle, pipeHandle); } else { err = uv_translate_sys_error(GET_REQ_ERROR(req)); } @@ -2603,9 +3054,25 @@ int uv_pipe_pending_count(uv_pipe_t* handle) { int uv_pipe_getsockname(const uv_pipe_t* handle, char* buffer, size_t* size) { + size_t len; + if (buffer == NULL || size == NULL || *size == 0) return UV_EINVAL; + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + if (handle->pathname != NULL) { + len = strlen(handle->pathname); + if (len > *size) { + return UV_ENOBUFS; + } + + *size = len; + memcpy(buffer, handle->pathname, len); + } + + return 0; + } + if (handle->flags & UV_HANDLE_BOUND) return uv__pipe_getname(handle, buffer, size); @@ -2620,6 +3087,8 @@ int uv_pipe_getsockname(const uv_pipe_t* handle, char* buffer, size_t* size) { int uv_pipe_getpeername(const uv_pipe_t* handle, char* buffer, size_t* size) { + size_t len; + if (buffer == NULL || size == NULL || *size == 0) return UV_EINVAL; @@ -2627,6 +3096,20 @@ int uv_pipe_getpeername(const uv_pipe_t* handle, char* buffer, size_t* size) { if (handle->flags & UV_HANDLE_BOUND) return UV_ENOTCONN; + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + if (handle->pathname != NULL) { + len = strlen(handle->pathname); + if (len > *size) { + return UV_ENOBUFS; + } + + *size = len; + memcpy(buffer, handle->pathname, len); + } + + return 0; + } + if (handle->handle != INVALID_HANDLE_VALUE) return uv__pipe_getname(handle, buffer, size); @@ -2659,6 +3142,11 @@ int uv_pipe_chmod(uv_pipe_t* handle, int mode) { if (handle == NULL || handle->handle == INVALID_HANDLE_VALUE) return UV_EBADF; + if (handle->flags & UV_HANDLE_WIN_UDS_PIPE) { + /* Unix domain socket doesn't support this. */ + return UV_ENOSYS; + } + if (mode != UV_READABLE && mode != UV_WRITABLE && mode != (UV_WRITABLE | UV_READABLE)) diff --git a/src/win/tcp.c b/src/win/tcp.c index 66e005ff0..e21458f4b 100644 --- a/src/win/tcp.c +++ b/src/win/tcp.c @@ -502,14 +502,14 @@ static void uv__tcp_queue_accept(uv_tcp_t* handle, uv_tcp_accept_t* req) { req->u.io.overlapped.hEvent = (HANDLE) ((ULONG_PTR) req->event_handle | 1); } - success = handle->tcp.serv.func_acceptex(handle->socket, - accept_socket, - (void*)req->accept_buffer, - 0, - sizeof(struct sockaddr_storage), - sizeof(struct sockaddr_storage), - &bytes, - &req->u.io.overlapped); + success = uv_wsa_acceptex(handle->socket, + accept_socket, + (void*)req->accept_buffer, + 0, + sizeof(struct sockaddr_storage), + sizeof(struct sockaddr_storage), + &bytes, + &req->u.io.overlapped); if (UV_SUCCEEDED_WITHOUT_IOCP(success)) { /* Process the req without IOCP. */ @@ -646,10 +646,8 @@ int uv__tcp_listen(uv_tcp_t* handle, int backlog, uv_connection_cb cb) { return handle->delayed_error; } - if (!handle->tcp.serv.func_acceptex) { - if (!uv__get_acceptex_function(handle->socket, &handle->tcp.serv.func_acceptex)) { - return WSAEAFNOSUPPORT; - } + if (uv_wsa_acceptex == NULL) { + return WSAEAFNOSUPPORT; } /* If this flag is set, we already made this listen call in xfer. */ @@ -855,10 +853,8 @@ static int uv__tcp_try_connect(uv_connect_t* req, goto out; } - if (!handle->tcp.conn.func_connectex) { - if (!uv__get_connectex_function(handle->socket, &handle->tcp.conn.func_connectex)) { - return WSAEAFNOSUPPORT; - } + if (uv_wsa_connectex == NULL) { + return WSAEAFNOSUPPORT; } /* This makes connect() fail instantly if the target port on the localhost @@ -895,13 +891,13 @@ out: return 0; } - success = handle->tcp.conn.func_connectex(handle->socket, - (const struct sockaddr*) &converted, - addrlen, - NULL, - 0, - &bytes, - &req->u.io.overlapped); + success = uv_wsa_connectex(handle->socket, + (const struct sockaddr*)&converted, + addrlen, + NULL, + 0, + &bytes, + &req->u.io.overlapped); if (UV_SUCCEEDED_WITHOUT_IOCP(success)) { /* Process the req without IOCP. */ @@ -1633,7 +1629,6 @@ int uv_socketpair(int type, int protocol, uv_os_sock_t fds[2], int flags0, int f SOCKET client0 = INVALID_SOCKET; SOCKET client1 = INVALID_SOCKET; SOCKADDR_IN name; - LPFN_ACCEPTEX func_acceptex; WSAOVERLAPPED overlap; char accept_buffer[sizeof(struct sockaddr_storage) * 2 + 32]; int namelen; @@ -1676,19 +1671,19 @@ int uv_socketpair(int type, int protocol, uv_os_sock_t fds[2], int flags0, int f goto wsaerror; if (!SetHandleInformation((HANDLE) client1, HANDLE_FLAG_INHERIT, 0)) goto error; - if (!uv__get_acceptex_function(server, &func_acceptex)) { + if (uv_wsa_acceptex == NULL) { err = WSAEAFNOSUPPORT; goto cleanup; } memset(&overlap, 0, sizeof(overlap)); - if (!func_acceptex(server, - client1, - accept_buffer, - 0, - sizeof(struct sockaddr_storage), - sizeof(struct sockaddr_storage), - &bytes, - &overlap)) { + if (!uv_wsa_acceptex(server, + client1, + accept_buffer, + 0, + sizeof(struct sockaddr_storage), + sizeof(struct sockaddr_storage), + &bytes, + &overlap)) { err = WSAGetLastError(); if (err == ERROR_IO_PENDING) { /* Result should complete immediately, since we already called connect, diff --git a/src/win/winsock.c b/src/win/winsock.c index a68b09536..98da2e7c2 100644 --- a/src/win/winsock.c +++ b/src/win/winsock.c @@ -34,6 +34,10 @@ int uv_tcp_non_ifs_lsp_ipv6; struct sockaddr_in uv_addr_ip4_any_; struct sockaddr_in6 uv_addr_ip6_any_; +/* WSA function pointers */ +LPFN_ACCEPTEX uv_wsa_acceptex; +LPFN_CONNECTEX uv_wsa_connectex; + /* * Retrieves the pointer to a winsock extension function. @@ -62,19 +66,6 @@ static BOOL uv__get_extension_function(SOCKET socket, GUID guid, } -BOOL uv__get_acceptex_function(SOCKET socket, LPFN_ACCEPTEX* target) { - const GUID wsaid_acceptex = WSAID_ACCEPTEX; - return uv__get_extension_function(socket, wsaid_acceptex, (void**)target); -} - - -BOOL uv__get_connectex_function(SOCKET socket, LPFN_CONNECTEX* target) { - const GUID wsaid_connectex = WSAID_CONNECTEX; - return uv__get_extension_function(socket, wsaid_connectex, (void**)target); -} - - - void uv__winsock_init(void) { WSADATA wsa_data; int errorno; @@ -82,6 +73,9 @@ void uv__winsock_init(void) { WSAPROTOCOL_INFOW protocol_info; int opt_len; + const GUID wsaid_acceptex = WSAID_ACCEPTEX; + const GUID wsaid_connectex = WSAID_CONNECTEX; + /* Set implicit binding address used by connectEx */ if (uv_ip4_addr("0.0.0.0", 0, &uv_addr_ip4_any_)) { abort(); @@ -131,6 +125,19 @@ void uv__winsock_init(void) { } closesocket(dummy); } + + /* Try to get WSA function pointers */ + dummy = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (dummy != INVALID_SOCKET) { + if (!uv__get_extension_function( + dummy, wsaid_acceptex, (void**)&uv_wsa_acceptex) || + !uv__get_extension_function( + dummy, wsaid_connectex, (void**)&uv_wsa_connectex)) { + uv_fatal_error(WSAGetLastError(), "WSAIoctl"); + } + + closesocket(dummy); + } } diff --git a/test/test-list.h b/test/test-list.h index b417f2b87..8c1ee0a93 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -224,6 +224,11 @@ TEST_DECLARE (pipe_getsockname_long_path) TEST_DECLARE (pipe_pending_instances) TEST_DECLARE (pipe_sendmsg) TEST_DECLARE (pipe_server_close) +#ifdef _WIN32 +TEST_DECLARE (pipe_win_uds) +TEST_DECLARE (pipe_win_uds_bad_name) +TEST_DECLARE (pipe_win_uds_shutdown) +#endif TEST_DECLARE (connection_fail) TEST_DECLARE (connection_fail_doesnt_auto_close) TEST_DECLARE (shutdown_close_tcp) @@ -642,6 +647,12 @@ TASK_LIST_START #endif /* Seems to be either about 0.5s or 5s, depending on the OS. */ TEST_ENTRY_CUSTOM (pipe_set_non_blocking, 0, 0, 20000) +#ifdef _WIN32 + TEST_ENTRY (pipe_win_uds) + TEST_ENTRY (pipe_win_uds_bad_name) + TEST_ENTRY (pipe_win_uds_shutdown) +#endif + TEST_ENTRY (pipe_set_chmod) TEST_ENTRY (tty) #ifdef _WIN32 diff --git a/test/test-pipe-win-uds.c b/test/test-pipe-win-uds.c new file mode 100644 index 000000000..09fd53ec1 --- /dev/null +++ b/test/test-pipe-win-uds.c @@ -0,0 +1,221 @@ +/* Copyright libuv contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#ifdef _WIN32 + +#if !defined(__MINGW32__) && !defined(__MINGW64__) +#define UV_SUPPORTS_WIN_UDS +#endif + +static int use_shutdown; +static uv_shutdown_t shutdown_client; + +static int close_cb_called; +static int shutdown_cb_called; +static int server_connect_cb_called; +static int client_connect_cb_called; + +static uv_pipe_t pipe_server; +static uv_pipe_t pipe_client; +static const char pipe_test_data[] = "send test through win uds pipe"; + + +static void alloc_cb(uv_handle_t* handle, size_t size, uv_buf_t* buf) { + buf->base = malloc(size); + buf->len = size; +} + + +static void close_cb(uv_handle_t* handle) { + ASSERT_NOT_NULL(handle); + close_cb_called++; +} + + +static void shutdown_cb(uv_shutdown_t* req, int status) { + ASSERT_NOT_NULL(req); + uv_close((uv_handle_t*) req->handle, close_cb); + shutdown_cb_called++; +} + + +static void after_write_cb(uv_write_t* req, int status) { + ASSERT_OK(status); + free(req->data); + free(req); +} + + +static void client_connect_cb(uv_connect_t* connect_req, int status) { + uv_buf_t bufs[1]; + uv_write_t* req; + + ASSERT_EQ(status, 0); + client_connect_cb_called++; + + // Server connected, send test data. + bufs[0] = uv_buf_init(pipe_test_data, strlen(pipe_test_data)); + req = malloc(sizeof(*req)); + req->data = NULL; + uv_write(req, connect_req->handle, bufs, 1, after_write_cb); +} + + +static void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + // Ignore read error. + if (nread < 0 || !buf) + return; + + // Test if data equal. + ASSERT_EQ(nread, (int) sizeof(pipe_test_data) - 1); + ASSERT_MEM_EQ(buf->base, pipe_test_data, nread); + + if (use_shutdown) { + uv_shutdown(&shutdown_client, (uv_stream_t*) &pipe_client, shutdown_cb); + } else { + uv_close((uv_handle_t*) &pipe_client, close_cb); + } + + uv_close((uv_handle_t*) &pipe_server, close_cb); +} + + +static void server_connect_cb(uv_stream_t* handle, int status) { + uv_pipe_t* conn; + + ASSERT_EQ(status, 0); + server_connect_cb_called++; + + // Client accepted, start reading. + conn = malloc(sizeof(*conn)); + ASSERT_OK(uv_pipe_init_ex(handle->loop, conn, UV_PIPE_INIT_WIN_UDS)); + ASSERT_OK(uv_accept(handle, (uv_stream_t*) conn)); + ASSERT_OK(uv_read_start((uv_stream_t*) conn, alloc_cb, read_cb)); +} + + +int test_pipe_win_uds() { +#ifndef UV_SUPPORTS_WIN_UDS + RETURN_SKIP("Windows-only test"); +#endif + int r; + uv_fs_t fs; + uv_connect_t req; + size_t size = MAX_PATH; + + char path_temp[MAX_PATH]; + char path[MAX_PATH]; + + // The windows UDS needs to be created on disk, create in temp dir. + r = uv_os_tmpdir(path_temp, &size); + ASSERT_OK(r); + snprintf(path, sizeof(path), "%s\\uv_pipe_win_uds", path_temp); + + // Remove the existing file, the file must not exist before server bind. + uv_fs_unlink(uv_default_loop(), &fs, path, NULL); + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + // Bind server + r = uv_pipe_init_ex(uv_default_loop(), &pipe_server, UV_PIPE_INIT_WIN_UDS); + ASSERT_OK(r); + r = uv_pipe_bind(&pipe_server, path); + ASSERT_OK(r); + uv_listen((uv_stream_t*) &pipe_server, SOMAXCONN, server_connect_cb); + uv_read_start((uv_stream_t*) &pipe_server, alloc_cb, read_cb); + + // Connect client to server + r = uv_pipe_init_ex(uv_default_loop(), &pipe_client, UV_PIPE_INIT_WIN_UDS); + ASSERT_OK(r); + uv_pipe_connect(&req, &pipe_client, path, client_connect_cb); + + // Run the loop + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + if (use_shutdown) { + ASSERT_EQ(1, shutdown_cb_called); + } + + ASSERT_EQ(2, close_cb_called); + ASSERT_EQ(1, server_connect_cb_called); + ASSERT_EQ(1, client_connect_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + + +TEST_IMPL(pipe_win_uds) { + use_shutdown = 0; + return test_pipe_win_uds(); +} + + +TEST_IMPL(pipe_win_uds_shutdown) { + use_shutdown = 1; + return test_pipe_win_uds(); +} + + +static void bad_name_connect_cb(uv_connect_t* connect_req, int status) { + ASSERT_EQ(status, UV_ENOENT); +} + + +TEST_IMPL(pipe_win_uds_bad_name) { +#ifndef UV_SUPPORTS_WIN_UDS + RETURN_SKIP("Windows-only test"); +#endif + int r; + uv_connect_t req; + uv_pipe_t pipe_server_1; + uv_pipe_t pipe_server_2; + uv_pipe_t pipe_client_1; + const char* path_1 = "not/exist/file/path"; + const char* path_2 = "test/fixtures/empty_file"; + + // Bind server 1 which has a bad path + r = uv_pipe_init_ex(uv_default_loop(), &pipe_server_1, UV_PIPE_INIT_WIN_UDS); + ASSERT_OK(r); + r = uv_pipe_bind(&pipe_server_1, path_1); + ASSERT_EQ(r, UV_EINVAL); + + // Bind server 2 which file exists + r = uv_pipe_init_ex(uv_default_loop(), &pipe_server_2, UV_PIPE_INIT_WIN_UDS); + ASSERT_OK(r); + r = uv_pipe_bind(&pipe_server_2, path_2); + ASSERT_EQ(r, UV_EEXIST); + + // Connect client to server with bad name + r = uv_pipe_init_ex(uv_default_loop(), &pipe_client_1, UV_PIPE_INIT_WIN_UDS); + ASSERT_OK(r); + uv_pipe_connect(&req, &pipe_client_1, path_1, bad_name_connect_cb); + + // Run the loop + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + +#endif