From b9b6db052b82dd0a429091238181195ec785ac0b Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 4 Jun 2023 22:43:14 +0200 Subject: [PATCH] linux: support abstract unix sockets (#4030) Add two new APIs for binding and connecting to abstract UNIX sockets. Fixes: https://github.com/libuv/libuv/issues/4028 --- docs/src/pipe.rst | 48 +++++++++++++++++-- include/uv.h | 10 ++++ include/uv/unix.h | 2 +- src/unix/pipe.c | 88 +++++++++++++++++++++++++++++------ src/win/pipe.c | 66 ++++++++++++++++++++++---- test/test-pipe-getsockname.c | 89 ++++++++++++++++++------------------ 6 files changed, 231 insertions(+), 72 deletions(-) diff --git a/docs/src/pipe.rst b/docs/src/pipe.rst index 5fa83b80..9d95b206 100644 --- a/docs/src/pipe.rst +++ b/docs/src/pipe.rst @@ -55,17 +55,55 @@ API Bind the pipe to a file path (Unix) or a name (Windows). + Does not support Linux abstract namespace sockets, + unlike :c:func:`uv_pipe_bind2`. + + Alias for ``uv_pipe_bind2(handle, name, strlen(name), 0)``. + .. note:: - Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, typically between - 92 and 108 bytes. + Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, + typically between 92 and 108 bytes. + +.. c:function:: int uv_pipe_bind2(uv_pipe_t* handle, const char* name, size_t namelen, unsigned int flags) + + Bind the pipe to a file path (Unix) or a name (Windows). + + ``flags`` must be zero. Returns ``UV_EINVAL`` for unsupported flags + without performing the bind operation. + + Supports Linux abstract namespace sockets. ``namelen`` must include + the leading nul byte but not the trailing nul byte. + + .. note:: + Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, + typically between 92 and 108 bytes. .. c:function:: void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, const char* name, uv_connect_cb cb) - Connect to the Unix domain socket or the named pipe. + Connect to the Unix domain socket or the Windows named pipe. + + Does not support Linux abstract namespace sockets, + unlike :c:func:`uv_pipe_connect2`. + + Alias for ``uv_pipe_connect2(req, handle, name, strlen(name), 0, cb)``. .. note:: - Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, typically between - 92 and 108 bytes. + Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, + typically between 92 and 108 bytes. + +.. c:function:: void uv_pipe_connect2(uv_connect_t* req, uv_pipe_t* handle, const char* name, size_t namelen, unsigned int flags, uv_connect_cb cb) + + Connect to the Unix domain socket or the Windows named pipe. + + ``flags`` must be zero. Returns ``UV_EINVAL`` for unsupported flags + without performing the connect operation. + + Supports Linux abstract namespace sockets. ``namelen`` must include + the leading nul byte but not the trailing nul byte. + + .. note:: + Paths on Unix get truncated to ``sizeof(sockaddr_un.sun_path)`` bytes, + typically between 92 and 108 bytes. .. c:function:: int uv_pipe_getsockname(const uv_pipe_t* handle, char* buffer, size_t* size) diff --git a/include/uv.h b/include/uv.h index 90795c6e..0f624f4d 100644 --- a/include/uv.h +++ b/include/uv.h @@ -823,10 +823,20 @@ struct uv_pipe_s { UV_EXTERN int uv_pipe_init(uv_loop_t*, uv_pipe_t* handle, int ipc); 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, + const char* name, + size_t namelen, + unsigned int flags); UV_EXTERN void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, const char* name, uv_connect_cb cb); +UV_EXTERN int uv_pipe_connect2(uv_connect_t* req, + uv_pipe_t* handle, + const char* name, + size_t namelen, + unsigned int flags, + uv_connect_cb cb); UV_EXTERN int uv_pipe_getsockname(const uv_pipe_t* handle, char* buffer, size_t* size); diff --git a/include/uv/unix.h b/include/uv/unix.h index d1a5f043..7944eff2 100644 --- a/include/uv/unix.h +++ b/include/uv/unix.h @@ -304,7 +304,7 @@ typedef struct { struct uv__queue write_completed_queue; \ #define UV_PIPE_PRIVATE_FIELDS \ - const char* pipe_fname; /* strdup'ed */ + const char* pipe_fname; /* NULL or strdup'ed */ #define UV_POLL_PRIVATE_FIELDS \ uv__io_t io_watcher; diff --git a/src/unix/pipe.c b/src/unix/pipe.c index 3b7bc881..823deb3d 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -41,26 +41,56 @@ int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) { int uv_pipe_bind(uv_pipe_t* handle, const char* name) { + return uv_pipe_bind2(handle, name, strlen(name), 0); +} + + +int uv_pipe_bind2(uv_pipe_t* handle, + const char* name, + size_t namelen, + unsigned int flags) { struct sockaddr_un saddr; - const char* pipe_fname; + char* pipe_fname; int sockfd; int err; pipe_fname = NULL; + if (flags != 0) + return UV_EINVAL; + + if (name == NULL) + return UV_EINVAL; + + if (namelen == 0) + return UV_EINVAL; + +#ifndef __linux__ + /* Abstract socket namespace only works on Linux. */ + if (*name == '\0') + return UV_EINVAL; +#endif + + /* Truncate long paths. Documented behavior. */ + if (namelen > sizeof(saddr.sun_path)) + namelen = sizeof(saddr.sun_path); + /* Already bound? */ if (uv__stream_fd(handle) >= 0) return UV_EINVAL; - if (uv__is_closing(handle)) { - return UV_EINVAL; - } - /* Make a copy of the file name, it outlives this function's scope. */ - pipe_fname = uv__strdup(name); - if (pipe_fname == NULL) - return UV_ENOMEM; - /* We've got a copy, don't touch the original any more. */ - name = NULL; + if (uv__is_closing(handle)) + return UV_EINVAL; + + /* Make a copy of the file path unless it is an abstract socket. + * We unlink the file later but abstract sockets disappear + * automatically since they're not real file system entities. + */ + if (*name != '\0') { + pipe_fname = uv__strdup(name); + if (pipe_fname == NULL) + return UV_ENOMEM; + } err = uv__socket(AF_UNIX, SOCK_STREAM, 0); if (err < 0) @@ -68,7 +98,7 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { sockfd = err; memset(&saddr, 0, sizeof saddr); - uv__strscpy(saddr.sun_path, pipe_fname, sizeof(saddr.sun_path)); + memcpy(&saddr.sun_path, name, namelen); saddr.sun_family = AF_UNIX; if (bind(sockfd, (struct sockaddr*)&saddr, sizeof saddr)) { @@ -83,12 +113,12 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { /* Success. */ handle->flags |= UV_HANDLE_BOUND; - handle->pipe_fname = pipe_fname; /* Is a strdup'ed copy. */ + handle->pipe_fname = pipe_fname; /* NULL or a strdup'ed copy. */ handle->io_watcher.fd = sockfd; return 0; err_socket: - uv__free((void*)pipe_fname); + uv__free(pipe_fname); return err; } @@ -176,11 +206,40 @@ void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, const char* name, uv_connect_cb cb) { + uv_pipe_connect2(req, handle, name, strlen(name), 0, cb); +} + + +int uv_pipe_connect2(uv_connect_t* req, + uv_pipe_t* handle, + const char* name, + size_t namelen, + unsigned int flags, + uv_connect_cb cb) { struct sockaddr_un saddr; int new_sock; int err; int r; + if (flags != 0) + return UV_EINVAL; + + if (name == NULL) + return UV_EINVAL; + + if (namelen == 0) + return UV_EINVAL; + +#ifndef __linux__ + /* Abstract socket namespace only works on Linux. */ + if (*name == '\0') + return UV_EINVAL; +#endif + + /* Truncate long paths. Documented behavior. */ + if (namelen > sizeof(saddr.sun_path)) + namelen = sizeof(saddr.sun_path); + new_sock = (uv__stream_fd(handle) == -1); if (new_sock) { @@ -191,7 +250,7 @@ void uv_pipe_connect(uv_connect_t* req, } memset(&saddr, 0, sizeof saddr); - uv__strscpy(saddr.sun_path, name, sizeof(saddr.sun_path)); + memcpy(&saddr.sun_path, name, namelen); saddr.sun_family = AF_UNIX; do { @@ -236,6 +295,7 @@ out: if (err) uv__io_feed(handle->loop, &handle->io_watcher); + return 0; } diff --git a/src/win/pipe.c b/src/win/pipe.c index 6044259c..a3daa3b7 100644 --- a/src/win/pipe.c +++ b/src/win/pipe.c @@ -694,20 +694,42 @@ void uv_pipe_pending_instances(uv_pipe_t* handle, int count) { /* Creates a pipe server. */ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { + return uv_pipe_bind2(handle, name, strlen(name), 0); +} + + +int uv_pipe_bind2(uv_pipe_t* handle, + const char* name, + size_t namelen, + unsigned int flags) { uv_loop_t* loop = handle->loop; int i, err, nameSize; uv_pipe_accept_t* req; + if (flags != 0) { + return UV_EINVAL; + } + + if (name == NULL) { + return UV_EINVAL; + } + + if (namelen == 0) { + return UV_EINVAL; + } + + if (*name == '\0') { + return UV_EINVAL; + } + if (handle->flags & UV_HANDLE_BOUND) { return UV_EINVAL; } - if (!name) { - return UV_EINVAL; - } if (uv__is_closing(handle)) { return UV_EINVAL; } + if (!(handle->flags & UV_HANDLE_PIPESERVER)) { handle->pipe.serv.pending_instances = default_pending_pipe_instances; } @@ -818,13 +840,41 @@ static DWORD WINAPI pipe_connect_thread_proc(void* parameter) { } -void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, - const char* name, uv_connect_cb cb) { +void uv_pipe_connect(uv_connect_t* req, + uv_pipe_t* handle, + const char* name, + uv_connect_cb cb) { + uv_pipe_connect2(req, handle, name, strlen(name), 0, cb); +} + + +int uv_pipe_connect2(uv_connect_t* req, + uv_pipe_t* handle, + const char* name, + size_t namelen, + unsigned int flags, + uv_connect_cb cb) { uv_loop_t* loop = handle->loop; int err, nameSize; HANDLE pipeHandle = INVALID_HANDLE_VALUE; DWORD duplex_flags; + if (flags != 0) { + return UV_EINVAL; + } + + if (name == NULL) { + return UV_EINVAL; + } + + if (namelen == 0) { + return UV_EINVAL; + } + + if (*name == '\0') { + return UV_EINVAL; + } + UV_REQ_INIT(req, UV_CONNECT); req->handle = (uv_stream_t*) handle; req->cb = cb; @@ -882,7 +932,7 @@ void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, REGISTER_HANDLE_REQ(loop, handle, req); handle->reqs_pending++; - return; + return 0; } err = GetLastError(); @@ -895,7 +945,7 @@ void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, uv__insert_pending_req(loop, (uv_req_t*) req); handle->reqs_pending++; REGISTER_HANDLE_REQ(loop, handle, req); - return; + return 0; error: if (handle->name) { @@ -911,7 +961,7 @@ error: uv__insert_pending_req(loop, (uv_req_t*) req); handle->reqs_pending++; REGISTER_HANDLE_REQ(loop, handle, req); - return; + return 0; } diff --git a/test/test-pipe-getsockname.c b/test/test-pipe-getsockname.c index 4b0aa53b..d749933d 100644 --- a/test/test-pipe-getsockname.c +++ b/test/test-pipe-getsockname.c @@ -25,11 +25,6 @@ #include #include -#if defined(__linux__) - #include - #include -#endif - #ifndef _WIN32 # include /* close */ #else @@ -63,8 +58,14 @@ static void pipe_client_connect_cb(uv_connect_t* req, int status) { r = uv_pipe_getpeername(&pipe_client, buf, &len); ASSERT(r == 0); - ASSERT(buf[len - 1] != 0); - ASSERT(memcmp(buf, TEST_PIPENAME, len) == 0); + if (*buf == '\0') { /* Linux abstract socket. */ + const char expected[] = "\0" TEST_PIPENAME; + ASSERT_GE(len, sizeof(expected)); + ASSERT_MEM_EQ(buf, expected, sizeof(expected)); + } else { + ASSERT_NE(0, buf[len - 1]); + ASSERT_MEM_EQ(buf, TEST_PIPENAME, len); + } len = sizeof buf; r = uv_pipe_getsockname(&pipe_client, buf, &len); @@ -72,7 +73,6 @@ static void pipe_client_connect_cb(uv_connect_t* req, int status) { pipe_client_connect_cb_called++; - uv_close((uv_handle_t*) &pipe_client, pipe_close_cb); uv_close((uv_handle_t*) &pipe_server, pipe_close_cb); } @@ -162,47 +162,48 @@ TEST_IMPL(pipe_getsockname) { TEST_IMPL(pipe_getsockname_abstract) { + /* TODO(bnoordhuis) Use unique name, susceptible to concurrent test runs. */ + static const char name[] = "\0" TEST_PIPENAME; #if defined(__linux__) - char buf[1024]; - size_t len; - int r; - int sock; - struct sockaddr_un sun; - socklen_t sun_len; - char abstract_pipe[] = "\0test-pipe"; + char buf[256]; + size_t buflen; - sock = socket(AF_UNIX, SOCK_STREAM, 0); - ASSERT(sock != -1); - - sun_len = sizeof sun; - memset(&sun, 0, sun_len); - sun.sun_family = AF_UNIX; - memcpy(sun.sun_path, abstract_pipe, sizeof abstract_pipe); - - r = bind(sock, (struct sockaddr*)&sun, sun_len); - ASSERT(r == 0); - - r = uv_pipe_init(uv_default_loop(), &pipe_server, 0); - ASSERT(r == 0); - r = uv_pipe_open(&pipe_server, sock); - ASSERT(r == 0); - - len = sizeof buf; - r = uv_pipe_getsockname(&pipe_server, buf, &len); - ASSERT(r == 0); - - ASSERT(memcmp(buf, abstract_pipe, sizeof abstract_pipe) == 0); - - uv_close((uv_handle_t*)&pipe_server, pipe_close_cb); - - uv_run(uv_default_loop(), UV_RUN_DEFAULT); - - close(sock); - - ASSERT(pipe_close_cb_called == 1); + buflen = sizeof(buf); + memset(buf, 0, sizeof(buf)); + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_server, 0)); + ASSERT_OK(uv_pipe_bind2(&pipe_server, name, sizeof(name), 0)); + ASSERT_OK(uv_pipe_getsockname(&pipe_server, buf, &buflen)); + ASSERT_MEM_EQ(name, buf, sizeof(name)); + ASSERT_OK(uv_listen((uv_stream_t*) &pipe_server, + 0, + pipe_server_connection_cb)); + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_client, 0)); + ASSERT_OK(uv_pipe_connect2(&connect_req, + &pipe_client, + name, + sizeof(name), + 0, + pipe_client_connect_cb)); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(1, pipe_client_connect_cb_called); + ASSERT_EQ(2, pipe_close_cb_called); MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; #else + /* On other platforms it should simply fail with UV_EINVAL. */ + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_server, 0)); + ASSERT_EQ(UV_EINVAL, uv_pipe_bind2(&pipe_server, name, sizeof(name), 0)); + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_client, 0)); + uv_close((uv_handle_t*) &pipe_server, pipe_close_cb); + ASSERT_EQ(UV_EINVAL, uv_pipe_connect2(&connect_req, + &pipe_client, + name, + sizeof(name), + 0, + (uv_connect_cb) abort)); + uv_close((uv_handle_t*) &pipe_client, pipe_close_cb); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(2, pipe_close_cb_called); MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; #endif