Repository: libuv/libuv (C)

Issue:
linux: abstract unix socket support
There is currently no way to specify `"\x00foo"` as the path to bind or connect to.

Needs two new functions that I'll hypothetically name:

- uv_pipe_bind2()
- uv_pipe_connect2()

Similar to their non-suffixed counterparts except they take `char* name, size_t len` instead of just `char* name`.


Discussion: Previous discussion: https://github.com/libuv/libuv/issues/1063



Fix (patch):
diff --git a/docs/src/pipe.rst b/docs/src/pipe.rst
index 5fa83b80d36..9d95b206a97 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.
+
+.. 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.
+        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.
+
+.. 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.
+        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 a68a42f1394..e739553ff77 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 d1a5f0438a5..7944eff2e4b 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 3b7bc881074..823deb3d975 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)) {
+
+  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;
+  /* 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 @@ void uv_pipe_connect(uv_connect_t* req,
   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 6044259c5f8..a3daa3b7a18 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 (handle->flags & UV_HANDLE_BOUND) {
+  if (flags != 0) {
+    return UV_EINVAL;
+  }
+
+  if (name == NULL) {
+    return UV_EINVAL;
+  }
+
+  if (namelen == 0) {
     return UV_EINVAL;
   }
 
-  if (!name) {
+  if (*name == '\0') {
+    return UV_EINVAL;
+  }
+
+  if (handle->flags & UV_HANDLE_BOUND) {
     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 @@ 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;
 }