https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=4cd57934be32395107960625c207854db949d7b7

commit 4cd57934be32395107960625c207854db949d7b7
Author: Corinna Vinschen <cori...@vinschen.de>
Date:   Tue Mar 6 18:55:03 2018 +0100

    Cygwin: AF_UNIX: Implement listen, accept4, connect, and others
    
    * Implement helper functions
    * Improve bind
    * Implement setting blocking, ioctl(FIONBIO), fcntl(F_SETFL)
    * Implement close_on_exec and fixup_after_fork
    * Allow overriding sun_path and peer_sun_path
    * Improve comments
    
    Signed-off-by: Corinna Vinschen <cori...@vinschen.de>

Diff:
---
 winsup/cygwin/fhandler.h              |  56 ++-
 winsup/cygwin/fhandler_socket_unix.cc | 781 ++++++++++++++++++++++++++++++++--
 winsup/cygwin/globals.cc              |   2 +
 3 files changed, 774 insertions(+), 65 deletions(-)

diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h
index fc83179..392ba6c 100644
--- a/winsup/cygwin/fhandler.h
+++ b/winsup/cygwin/fhandler.h
@@ -857,28 +857,43 @@ class fhandler_socket_unix : public fhandler_socket
      NPFS_DEVICE,
      NPFS_DIR
    };
- protected:
-   HANDLE file;        /* Either NT symlink or reparse point */
-
-   HANDLE create_abstract_link (const sun_name_t *sun,
-                               PUNICODE_STRING pipe_name);
-   HANDLE create_reparse_point (const sun_name_t *sun,
-                               PUNICODE_STRING pipe_name);
-   HANDLE create_file (const sun_name_t *sun);
-   int open_abstract_link (sun_name_t *sun, PUNICODE_STRING pipe_name);
-   int open_reparse_point (sun_name_t *sun, PUNICODE_STRING pipe_name);
-   int open_file (sun_name_t *sun, int &type, PUNICODE_STRING pipe_name);
-   HANDLE autobind (sun_name_t *sun);
-   wchar_t get_type_char ();
-   void gen_pipe_name ();
-   void set_wait_state (DWORD wait_state);
-   HANDLE create_pipe ();
-   HANDLE create_pipe_instance ();
 
  protected:
+  SRWLOCK conn_lock;
+  SRWLOCK bind_lock;
+  SRWLOCK io_lock;
+  HANDLE backing_file_handle;  /* Either NT symlink or INVALID_HANDLE_VALUE,
+                                  if the socket is backed by a file in the
+                                  file system (actually a reparse point) */
+  HANDLE connect_wait_thr;
+  PVOID cwt_param;
+  LONG so_error;
   sun_name_t *sun_path;
   sun_name_t *peer_sun_path;
+  struct ucred peer_cred;
+
+  void gen_pipe_name ();
+  static HANDLE create_abstract_link (const sun_name_t *sun,
+                                     PUNICODE_STRING pipe_name);
+  static HANDLE create_reparse_point (const sun_name_t *sun,
+                                     PUNICODE_STRING pipe_name);
+  HANDLE create_file (const sun_name_t *sun);
+  static int open_abstract_link (sun_name_t *sun, PUNICODE_STRING pipe_name);
+  static int open_reparse_point (sun_name_t *sun, PUNICODE_STRING pipe_name);
+  static int open_file (sun_name_t *sun, int &type, PUNICODE_STRING pipe_name);
+  HANDLE autobind (sun_name_t *sun);
+  wchar_t get_type_char ();
+  void set_pipe_non_blocking (bool nonblocking);
+  int send_my_name ();
+  int recv_peer_name ();
   static NTSTATUS npfs_handle (HANDLE &nph, npfs_hdl_t type);
+  HANDLE create_pipe ();
+  HANDLE create_pipe_instance ();
+  NTSTATUS open_pipe (HANDLE &ph, PUNICODE_STRING pipe_name);
+  int wait_pipe (PUNICODE_STRING pipe_name);
+  int connect_pipe (PUNICODE_STRING pipe_name);
+  int listen_pipe ();
+  int disconnect_pipe (HANDLE ph);
   sun_name_t *get_sun_path () {return sun_path;}
   sun_name_t *get_peer_sun_path () {return peer_sun_path;}
   void set_sun_path (struct sockaddr_un *un, __socklen_t unlen);
@@ -888,10 +903,9 @@ class fhandler_socket_unix : public fhandler_socket
   void set_peer_sun_path (sun_name_t *snt)
     { snt ? set_peer_sun_path (&snt->un, snt->un_len)
          : set_peer_sun_path (NULL, 0); }
-
- protected:
-  struct ucred peer_cred;
   void set_cred ();
+  void fixup_after_fork (HANDLE parent);
+  void set_close_on_exec (bool val);
 
  public:
   fhandler_socket_unix ();
@@ -899,6 +913,8 @@ class fhandler_socket_unix : public fhandler_socket
 
   int dup (fhandler_base *child, int);
 
+  DWORD wait_pipe_thread (PUNICODE_STRING pipe_name);
+
   int socket (int af, int type, int protocol, int flags);
   int socketpair (int af, int type, int protocol, int flags,
                  fhandler_socket *fh_out);
diff --git a/winsup/cygwin/fhandler_socket_unix.cc 
b/winsup/cygwin/fhandler_socket_unix.cc
index 9ea9f45..ae422dd 100644
--- a/winsup/cygwin/fhandler_socket_unix.cc
+++ b/winsup/cygwin/fhandler_socket_unix.cc
@@ -60,7 +60,70 @@
    Note: We use MAX_PATH below for convenience where sufficient.  It's
    big enough to hold sun_paths as well as pipe names so we don't have
    to use tmp_pathbuf as often.
+
+   Every packet sent to a peer is a combination of the socket name of the
+   local socket, the ancillary data, and the actual user data.  The data
+   is always sent in this order.  The header contains length information
+   for the entire packet, as well as for all three data blocks.  The
+   combined maximum size of a packet is 64K, including the header.
+
+   A connecting, bound STREAM socket send it's local sun_path once after
+   a successful connect.  An already connected socket also sends its local
+   sun_path after a successful bind (border case, but still...).  These
+   packages don't contain any other data (cmsg_len == 0, data_len == 0).
+
+   A bound DGRAM socket send its sun_path with each sendmsg/sendto.
 */
+class af_unix_pkt_hdr_t
+{
+ public:
+  uint16_t     pckt_len;       /* size of packet including header      */
+  uint8_t      shut_info;      /* shutdown info.  SHUT_RD means
+                                  SHUT_RD on the local side, so the
+                                  peer must not send further packets,
+                                  vice versa for SHUT_WR.   SHUT_RDWR
+                                  is followed by closing the pipe
+                                  handle. */
+  uint8_t      name_len;       /* size of name, a sockaddr_un          */
+  uint16_t     cmsg_len;       /* size of ancillary data block         */
+  uint16_t     data_len;       /* size of user data                    */
+
+  void init (uint8_t s, uint8_t n, uint16_t c, uint16_t d)
+    {
+      shut_info = s;
+      name_len = n;
+      cmsg_len = c;
+      data_len = d;
+      pckt_len = sizeof (*this) + name_len + cmsg_len + data_len;
+    }
+};
+
+#define AF_UNIX_PKT_OFFSETOF_NAME(phdr)        \
+       (sizeof (af_unix_pkt_hdr_t))
+#define AF_UNIX_PKT_OFFSETOF_CMSG(phdr)        \
+       (sizeof (af_unix_pkt_hdr_t) + (phdr)->name_len)
+#define AF_UNIX_PKT_OFFSETOF_DATA(phdr)        \
+       ({ \
+          af_unix_pkt_hdr_t *_p = phdr; \
+          sizeof (af_unix_pkt_hdr_t) + (_p)->name_len + (_p)->cmsg_len; \
+       })
+#define AF_UNIX_PKT_NAME(phdr) \
+       ({ \
+          af_unix_pkt_hdr_t *_p = phdr; \
+          (struct sockaddr_un *)(((PBYTE)(_p)) \
+                                 + AF_UNIX_PKT_OFFSETOF_NAME (_p)); \
+       })
+#define AF_UNIX_PKT_CMSG(phdr) \
+       ({ \
+          af_unix_pkt_hdr_t *_p = phdr; \
+          (void *)(((PBYTE)(_p)) + AF_UNIX_PKT_OFFSETOF_CMSG (_p)); \
+       })
+#define AF_UNIX_PKT_DATA(phdr) \
+       ({ \
+          af_unix_pkt_hdr_t _p = phdr; \
+          (void *)(((PBYTE)(_p)) + AF_UNIX_PKT_OFFSETOF_DATA (_p)); \
+       })
+
 /* Character length of pipe name, excluding trailing NUL. */
 #define CYGWIN_PIPE_SOCKET_NAME_LEN     47
 
@@ -439,7 +502,9 @@ fhandler_socket_unix::get_type_char ()
     }
 }
 
+/* This also sets the pipe to message mode unconditionally. */
 void
+fhandler_socket_unix::set_pipe_non_blocking (bool nonblocking)
 {
   if (get_handle ())
     {
@@ -448,11 +513,94 @@ void
       FILE_PIPE_INFORMATION fpi;
 
       fpi.ReadMode = FILE_PIPE_MESSAGE_MODE;
-      fpi.CompletionMode = wait_state;
+      fpi.CompletionMode = nonblocking ? FILE_PIPE_COMPLETE_OPERATION
+                                      : FILE_PIPE_QUEUE_OPERATION;
       status = NtSetInformationFile (get_handle (), &io, &fpi, sizeof fpi,
                                     FilePipeInformation);
       if (!NT_SUCCESS (status))
-       system_printf ("NtSetInformationFile(FilePipeInformation): %y", status);
+       debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status);
+    }
+}
+
+int
+fhandler_socket_unix::send_my_name ()
+{
+  size_t name_len = 0;
+  af_unix_pkt_hdr_t *packet;
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+
+  AcquireSRWLockShared (&bind_lock);
+  name_len = get_sun_path ()->un_len;
+  packet = (af_unix_pkt_hdr_t *) alloca (sizeof *packet + name_len);
+  memcpy (AF_UNIX_PKT_NAME (packet), &get_sun_path ()->un, name_len);
+  ReleaseSRWLockShared (&bind_lock);
+
+  packet->init (0, name_len, 0, 0);
+
+  /* The theory: Fire and forget. */
+  AcquireSRWLockExclusive (&io_lock);
+  set_pipe_non_blocking (true);
+  status = NtWriteFile (get_handle (), NULL, NULL, NULL, &io, packet,
+                       packet->pckt_len, NULL, NULL);
+  set_pipe_non_blocking (is_nonblocking ());
+  ReleaseSRWLockExclusive (&io_lock);
+  if (!NT_SUCCESS (status))
+    {
+      debug_printf ("Couldn't send my name: NtWriteFile: %y", status);
+      return -1;
+    }
+  return 0;
+}
+
+/* Returns an error code.  Locking is not required, user space doesn't know
+   about this socket yet. */
+int
+fhandler_socket_unix::recv_peer_name ()
+{
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+  af_unix_pkt_hdr_t *packet;
+  struct sockaddr_un *un;
+  ULONG len;
+  int ret = 0;
+
+  len = sizeof *packet + sizeof *un;
+  packet = (af_unix_pkt_hdr_t *) alloca (len);
+  set_pipe_non_blocking (false);
+  status = NtReadFile (get_handle (), NULL, NULL, NULL, &io, packet, len,
+                      NULL, NULL);
+  if (status == STATUS_PENDING)
+    {
+      DWORD ret;
+      LARGE_INTEGER timeout;
+
+      timeout.QuadPart = -20 * NS100PERSEC;    /* 20 secs */
+      ret = cygwait (connect_wait_thr, &timeout, cw_sig_eintr);
+      switch (ret)
+       {
+       case WAIT_OBJECT_0:
+         status = io.Status;
+         break;
+       case WAIT_TIMEOUT:
+         ret = ECONNABORTED;
+         break;
+       case WAIT_SIGNALED:
+         ret = EINTR;
+         break;
+       default:
+         ret = EPROTO;
+         break;
+       }
+    }
+  if (!NT_SUCCESS (status) && ret == 0)
+    ret = geterrno_from_nt_status (status);
+  if (ret == 0 && packet->name_len > 0)
+    set_peer_sun_path (AF_UNIX_PKT_NAME (packet), packet->name_len);
+  set_pipe_non_blocking (is_nonblocking ());
+  return ret;
+}
+
 NTSTATUS
 fhandler_socket_unix::npfs_handle (HANDLE &nph, npfs_hdl_t type)
 {
@@ -562,24 +710,189 @@ fhandler_socket_unix::create_pipe_instance ()
   return ph;
 }
 
-fhandler_socket_unix::fhandler_socket_unix () :
-  sun_path (NULL),
-  peer_sun_path (NULL)
+NTSTATUS
+fhandler_socket_unix::open_pipe (HANDLE &ph, PUNICODE_STRING pipe_name)
 {
-  set_cred ();
+  NTSTATUS status;
+  HANDLE npfsh;
+  ACCESS_MASK access;
+  OBJECT_ATTRIBUTES attr;
+  IO_STATUS_BLOCK io;
+  ULONG sharing;
+
+  status = npfs_handle (npfsh, NPFS_DIR);
+  if (!NT_SUCCESS (status))
+    return status;
+  access = GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE;
+  InitializeObjectAttributes (&attr, pipe_name, OBJ_INHERIT, npfsh, NULL);
+  sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+  status = NtOpenFile (&ph, access, &attr, &io, sharing, 0);
+  if (NT_SUCCESS (status))
+    {
+      set_io_handle (ph);
+      send_my_name ();
+    }
+  return status;
 }
 
-fhandler_socket_unix::~fhandler_socket_unix ()
+struct conn_wait_info_t
 {
-  if (sun_path)
-    delete sun_path;
-  if (peer_sun_path)
-    delete peer_sun_path;
+  fhandler_socket_unix *fh;
+  UNICODE_STRING pipe_name;
+  WCHAR pipe_name_buf[CYGWIN_PIPE_SOCKET_NAME_LEN + 1];
+};
+
+/* Just hop to the wait_pipe_thread method. */
+DWORD WINAPI
+connect_wait_func (LPVOID param)
+{
+  conn_wait_info_t *wait_info = (conn_wait_info_t *) param;
+  return wait_info->fh->wait_pipe_thread (&wait_info->pipe_name);
+}
+
+/* Start a waiter thread to wait for a pipe instance to become available.
+   in blocking mode, wait for the thread to finish.  In nonblocking mode
+   just return with errno set to EINPROGRESS. */
+int
+fhandler_socket_unix::wait_pipe (PUNICODE_STRING pipe_name)
+{
+  conn_wait_info_t *wait_info;
+  DWORD waitret, err;
+  int ret = -1;
+
+  wait_info = (conn_wait_info_t *)
+             cmalloc_abort (HEAP_FHANDLER, sizeof *wait_info);
+  wait_info->fh = this;
+  RtlInitEmptyUnicodeString (&wait_info->pipe_name, wait_info->pipe_name_buf,
+                            sizeof wait_info->pipe_name_buf);
+  RtlCopyUnicodeString (&wait_info->pipe_name, pipe_name);
+
+  cwt_param = (PVOID) wait_info;
+  connect_wait_thr = CreateThread (NULL, PREFERRED_IO_BLKSIZE,
+                                  connect_wait_func, cwt_param, 0, NULL);
+  if (!connect_wait_thr)
+    {
+      cfree (wait_info);
+      __seterrno ();
+      return -1;
+    }
+  if (is_nonblocking ())
+    {
+      set_errno (EINPROGRESS);
+      return -1;
+    }
+
+  waitret = cygwait (connect_wait_thr, cw_infinite, cw_cancel | cw_sig_eintr);
+  if (waitret == WAIT_OBJECT_0)
+    GetExitCodeThread (connect_wait_thr, &err);
+  else
+    TerminateThread (connect_wait_thr, 0);
+  HANDLE thr = InterlockedExchangePointer (&connect_wait_thr, NULL);
+  if (thr)
+    CloseHandle (thr);
+  PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
+  if (param)
+    cfree (param);
+  switch (waitret)
+    {
+    case WAIT_CANCELED:
+      pthread::static_cancel_self ();
+      /*NOTREACHED*/
+    case WAIT_SIGNALED:
+      set_errno (EINTR);
+      break;
+    default:
+      InterlockedExchange (&so_error, err);
+      if (err)
+       set_errno (err);
+      else
+       ret = 0;
+      break;
+    }
+  return ret;
+}
+
+int
+fhandler_socket_unix::connect_pipe (PUNICODE_STRING pipe_name)
+{
+  NTSTATUS status;
+  HANDLE ph = NULL;
+
+  /* Try connecting first.  If it doesn't work, wait for the pipe
+     to become available. */
+  status = open_pipe (ph, pipe_name);
+  if (status == STATUS_PIPE_BUSY)
+    return wait_pipe (pipe_name);
+  if (!NT_SUCCESS (status))
+    {
+      __seterrno_from_nt_status (status);
+      InterlockedExchange (&so_error, get_errno ());
+      return -1;
+    }
+  InterlockedExchange (&so_error, 0);
+  return 0;
+}
+
+int
+fhandler_socket_unix::listen_pipe ()
+{
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+  OBJECT_ATTRIBUTES attr;
+  HANDLE evt = NULL;
+
+  io.Status = STATUS_PENDING;
+  if (!is_nonblocking ())
+    {
+      /* Create event object and set APC context pointer. */
+      InitializeObjectAttributes (&attr, NULL, 0, NULL, NULL);
+      status = NtCreateEvent (&evt, EVENT_ALL_ACCESS, &attr,
+                             NotificationEvent, FALSE);
+      if (!NT_SUCCESS (status))
+       {
+         __seterrno_from_nt_status (status);
+         return -1;
+       }
+    }
+  status = NtFsControlFile (get_handle (), evt, NULL, NULL, &io,
+                           FSCTL_PIPE_LISTEN, NULL, 0, NULL, 0);
+  if (status == STATUS_PENDING)
+    {
+      if (cygwait (evt ?: get_handle ()) == WAIT_OBJECT_0)
+       status = io.Status;
+    }
+  if (evt)
+    NtClose (evt);
+  if (status == STATUS_PIPE_LISTENING)
+    set_errno (EAGAIN);
+  else if (status != STATUS_PIPE_CONNECTED)
+    __seterrno_from_nt_status (status);
+  return (status == STATUS_PIPE_CONNECTED) ? 0 : -1;
+}
+
+int
+fhandler_socket_unix::disconnect_pipe (HANDLE ph)
+{
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+
+  status = NtFsControlFile (ph, NULL, NULL, NULL, &io, FSCTL_PIPE_DISCONNECT,
+                           NULL, 0, NULL, 0);
+  if (status == STATUS_PENDING && cygwait (ph) == WAIT_OBJECT_0)
+    status = io.Status;
+  if (!NT_SUCCESS (status))
+    {
+      __seterrno_from_nt_status (status);
+      return -1;
+    }
+  return 0;
 }
 
 void
 fhandler_socket_unix::set_sun_path (struct sockaddr_un *un, socklen_t unlen)
 {
+  if (peer_sun_path)
+    delete peer_sun_path;
   if (!un)
     sun_path = NULL;
   sun_path = new sun_name_t ((const struct sockaddr *) un, unlen);
@@ -589,6 +902,8 @@ void
 fhandler_socket_unix::set_peer_sun_path (struct sockaddr_un *un,
                                         socklen_t unlen)
 {
+  if (peer_sun_path)
+    delete peer_sun_path;
   if (!un)
     peer_sun_path = NULL;
   peer_sun_path = new sun_name_t ((const struct sockaddr *) un, unlen);
@@ -602,15 +917,143 @@ fhandler_socket_unix::set_cred ()
   peer_cred.gid = (gid_t) -1;
 }
 
+void
+fhandler_socket_unix::fixup_after_fork (HANDLE parent)
+{
+  fhandler_socket::fixup_after_fork (parent);
+  if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE)
+    fork_fixup (parent, backing_file_handle, "backing_file_handle");
+  InitializeSRWLock (&conn_lock);
+  InitializeSRWLock (&bind_lock);
+  InitializeSRWLock (&io_lock);
+  connect_wait_thr = NULL;
+  cwt_param = NULL;
+}
+
+void
+fhandler_socket_unix::set_close_on_exec (bool val)
+{
+  fhandler_base::set_close_on_exec (val);
+  if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE)
+    set_no_inheritance (backing_file_handle, val);
+}
+
+/* ========================== public methods ========================= */
+
+fhandler_socket_unix::fhandler_socket_unix ()
+{
+  set_cred ();
+}
+
+fhandler_socket_unix::~fhandler_socket_unix ()
+{
+  if (sun_path)
+    delete sun_path;
+  if (peer_sun_path)
+    delete peer_sun_path;
+}
+
 int
 fhandler_socket_unix::dup (fhandler_base *child, int flags)
 {
   fhandler_socket_unix *fhs = (fhandler_socket_unix *) child;
   fhs->set_sun_path (get_sun_path ());
   fhs->set_peer_sun_path (get_peer_sun_path ());
+  InitializeSRWLock (&fhs->conn_lock);
+  InitializeSRWLock (&fhs->bind_lock);
+  InitializeSRWLock (&fhs->io_lock);
+  fhs->connect_wait_thr = NULL;
+  fhs->cwt_param = NULL;
   return fhandler_socket::dup (child, flags);
 }
 
+/* Waiter thread method.  Here we wait for a pipe instance to become
+   available and connect to it, if so.  This function is running
+   asynchronously if called on a non-blocking pipe.  The important
+   things to do:
+
+   - Set the peer pipe handle if successful
+   - Send own sun_path to peer if successful TODO
+   - Set connect_state
+   - Set so_error for later call to select
+*/
+DWORD
+fhandler_socket_unix::wait_pipe_thread (PUNICODE_STRING pipe_name)
+{
+  HANDLE npfsh;
+  LONG error = 0;
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+  ULONG pwbuf_size;
+  PFILE_PIPE_WAIT_FOR_BUFFER pwbuf;
+  LONGLONG stamp;
+  HANDLE ph = NULL;
+
+  status = npfs_handle (npfsh, NPFS_DEVICE);
+  if (!NT_SUCCESS (status))
+    {
+      error = geterrno_from_nt_status (status);
+      goto out;
+    }
+  pwbuf_size = offsetof (FILE_PIPE_WAIT_FOR_BUFFER, Name) + pipe_name->Length;
+  pwbuf = (PFILE_PIPE_WAIT_FOR_BUFFER) alloca (pwbuf_size);
+  pwbuf->Timeout.QuadPart = -20 * NS100PERSEC; /* 20 secs */
+  pwbuf->NameLength = pipe_name->Length;
+  pwbuf->TimeoutSpecified = TRUE;
+  memcpy (pwbuf->Name, pipe_name->Buffer, pipe_name->Length);
+  stamp = ntod.nsecs ();
+  do
+    {
+      status = NtFsControlFile (npfsh, NULL, NULL, NULL, &io, FSCTL_PIPE_WAIT,
+                               pwbuf, pwbuf_size, NULL, 0);
+      switch (status)
+       {
+         case STATUS_SUCCESS:
+           {
+             status = open_pipe (ph, pipe_name);
+             if (status == STATUS_PIPE_BUSY)
+               {
+                 /* Another concurrent connect grabbed the pipe instance
+                    under our nose.  Fix the timeout value and go waiting
+                    again, unless the timeout has passed. */
+                 pwbuf->Timeout.QuadPart -= (stamp - ntod.nsecs ()) / 100LL;
+                 if (pwbuf->Timeout.QuadPart >= 0)
+                   {
+                     status = STATUS_IO_TIMEOUT;
+                     error = ETIMEDOUT;
+                   }
+               }
+             else if (!NT_SUCCESS (status))
+               error = geterrno_from_nt_status (status);
+           }
+           break;
+         case STATUS_OBJECT_NAME_NOT_FOUND:
+           error = EADDRNOTAVAIL;
+           break;
+         case STATUS_IO_TIMEOUT:
+           error = ETIMEDOUT;
+           break;
+         case STATUS_INSUFFICIENT_RESOURCES:
+           error = ENOBUFS;
+           break;
+         case STATUS_INVALID_DEVICE_REQUEST:
+         default:
+           error = EIO;
+           break;
+       }
+    }
+  while (status == STATUS_PIPE_BUSY);
+out:
+  PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
+  if (param)
+    cfree (param);
+  AcquireSRWLockExclusive (&conn_lock);
+  InterlockedExchange (&so_error, error);
+  connect_state (error ? connect_failed : connected);
+  ReleaseSRWLockExclusive (&conn_lock);
+  return error;
+}
+
 int
 fhandler_socket_unix::socket (int af, int type, int protocol, int flags)
 {
@@ -656,6 +1099,9 @@ fhandler_socket_unix::socketpair (int af, int type, int 
protocol, int flags,
   return -1;
 }
 
+/* Bind creates the backing file, generates the pipe name and sets
+   bind_state.  On DGRAM sockets it also creates the pipe.  On STREAM
+   sockets either listen or connect will do that. */
 int
 fhandler_socket_unix::bind (const struct sockaddr *name, int namelen)
 {
@@ -663,46 +1109,253 @@ fhandler_socket_unix::bind (const struct sockaddr 
*name, int namelen)
   bool unnamed = (sun.un_len == sizeof sun.un.sun_family);
   HANDLE pipe = NULL;
 
-  /* If we have a handle, we're already bound. */
-  if (get_handle () || sun.un.sun_family != AF_UNIX)
+  if (sun.un.sun_family != AF_UNIX)
     {
       set_errno (EINVAL);
       return -1;
     }
+  AcquireSRWLockExclusive (&bind_lock);
+  if (binding_state () == bind_pending)
+    {
+      set_errno (EALREADY);
+      ReleaseSRWLockExclusive (&bind_lock);
+      return -1;
+    }
+  if (binding_state () == bound)
+    {
+      set_errno (EINVAL);
+      ReleaseSRWLockExclusive (&bind_lock);
+      return -1;
+    }
+  binding_state (bind_pending);
+  ReleaseSRWLockExclusive (&bind_lock);
   gen_pipe_name ();
-  pipe = create_pipe ();
-  if (!pipe)
-    return -1;
-  file = unnamed ? autobind (&sun) : create_file (&sun);
-  if (!file)
+  if (get_socket_type () == SOCK_DGRAM)
+    {
+      pipe = create_pipe ();
+      if (!pipe)
+       {
+         binding_state (unbound);
+         return -1;
+       }
+      set_io_handle (pipe);
+    }
+  backing_file_handle = unnamed ? autobind (&sun) : create_file (&sun);
+  if (!backing_file_handle)
     {
-      NtClose (pipe);
+      set_io_handle (NULL);
+      if (pipe)
+       NtClose (pipe);
+      binding_state (unbound);
       return -1;
     }
-  set_io_handle (pipe);
   set_sun_path (&sun);
+  /* If we're already connected, send name to peer. */
+  if (connect_state () == connected)
+    send_my_name ();
+  binding_state (bound);
   return 0;
 }
 
+/* Create pipe on non-DGRAM sockets and set conn_state to listener. */
 int
 fhandler_socket_unix::listen (int backlog)
 {
-  set_errno (EAFNOSUPPORT);
-  return -1;
+  if (get_socket_type () == SOCK_DGRAM)
+    {
+      set_errno (EOPNOTSUPP);
+      return -1;
+    }
+  AcquireSRWLockShared (&bind_lock);
+  while (binding_state () == bind_pending)
+    yield ();
+  if (binding_state () == unbound)
+    {
+      set_errno (EDESTADDRREQ);
+      ReleaseSRWLockShared (&bind_lock);
+      return -1;
+    }
+  ReleaseSRWLockShared (&bind_lock);
+  AcquireSRWLockExclusive (&conn_lock);
+  if (connect_state () != unconnected && connect_state () != connect_failed)
+    {
+      set_errno (connect_state () == listener ? EADDRINUSE : EINVAL);
+      ReleaseSRWLockExclusive (&conn_lock);
+      return -1;
+    }
+  if (get_socket_type () != SOCK_DGRAM)
+    {
+      HANDLE pipe = create_pipe ();
+      if (!pipe)
+       {
+         connect_state (unconnected);
+         return -1;
+       }
+      set_io_handle (pipe);
+    }
+  connect_state (listener);
+  ReleaseSRWLockExclusive (&conn_lock);
+  return 0;
 }
 
 int
 fhandler_socket_unix::accept4 (struct sockaddr *peer, int *len, int flags)
 {
-  set_errno (EAFNOSUPPORT);
+  if (get_socket_type () != SOCK_STREAM)
+    {
+      set_errno (EOPNOTSUPP);
+      return -1;
+    }
+  if (connect_state () != listener
+      || (peer && (!len || *len < (int) sizeof (sa_family_t))))
+    {
+      set_errno (EINVAL);
+      return -1;
+    }
+  if (listen_pipe () == 0)
+    {
+      /* Our handle is now connected with a client.  This handle is used
+         for the accepted socket.  Our handle has to be replaced with a
+        new instance handle for the next accept. */
+      HANDLE accepted = get_handle ();
+      HANDLE new_inst = create_pipe_instance ();
+      int error = ENOBUFS;
+      if (new_inst)
+       {
+         /* Set new io handle. */
+         set_io_handle (new_inst);
+         /* Prepare new file descriptor. */
+         cygheap_fdnew fd;
+
+         if (fd >= 0)
+           {
+             fhandler_socket_unix *sock = (fhandler_socket_unix *)
+                                          build_fh_dev (dev ());
+             if (sock)
+               {
+                 sock->set_addr_family (get_addr_family ());
+                 sock->set_socket_type (get_socket_type ());
+                 if (flags & SOCK_NONBLOCK)
+                   sock->set_nonblocking (true);
+                 if (flags & SOCK_CLOEXEC)
+                   sock->set_close_on_exec (true);
+                 sock->set_unique_id ();
+                 sock->set_ino (sock->get_unique_id ());
+                 sock->pc.set_nt_native_path (pc.get_nt_native_path ());
+                 sock->connect_state (connected);
+                 sock->binding_state (binding_state ());
+                 sock->set_io_handle (accepted);
+
+                 sock->set_sun_path (get_sun_path ());
+                 error = sock->recv_peer_name ();
+                 if (error == 0)
+                   {
+                     if (peer)
+                       {
+                         sun_name_t *sun = sock->get_peer_sun_path ();
+                         if (sun)
+                           {
+                             memcpy (peer, &sun->un, MIN (*len, sun->un_len));
+                             *len = sun->un_len;
+                           }
+                         else if (len)
+                           *len = 0;
+                       }
+                     fd = sock;
+                     if (fd <= 2)
+                       set_std_handle (fd);
+                     return fd;
+                   }
+                 delete sock;
+               }
+             fd.release ();
+           }
+       }
+      /* Ouch!  We can't handle the client if we couldn't
+        create a new instance to accept more connections.*/
+      disconnect_pipe (accepted);
+      set_errno (error);
+    }
   return -1;
 }
 
 int
 fhandler_socket_unix::connect (const struct sockaddr *name, int namelen)
 {
-  set_errno (EAFNOSUPPORT);
-  return -1;
+  sun_name_t sun (name, namelen);
+  int peer_type;
+  WCHAR pipe_name_buf[CYGWIN_PIPE_SOCKET_NAME_LEN + 1];
+  UNICODE_STRING pipe_name;
+
+  /* Test and set connection state. */
+  AcquireSRWLockExclusive (&conn_lock);
+  if (connect_state () == connect_pending)
+    {
+      set_errno (EALREADY);
+      ReleaseSRWLockExclusive (&conn_lock);
+      return -1;
+    }
+  if (connect_state () == listener)
+    {
+      set_errno (EADDRINUSE);
+      ReleaseSRWLockExclusive (&conn_lock);
+      return -1;
+    }
+  if (connect_state () == connected && get_socket_type () != SOCK_DGRAM)
+    {
+      set_errno (EISCONN);
+      ReleaseSRWLockExclusive (&conn_lock);
+      return -1;
+    }
+  connect_state (connect_pending);
+  ReleaseSRWLockExclusive (&conn_lock);
+  /* Check validity of name */
+  if (sun.un_len <= (int) sizeof (sa_family_t))
+    {
+      set_errno (EINVAL);
+      connect_state (unconnected);
+      return -1;
+    }
+  if (sun.un.sun_family != AF_UNIX)
+    {
+      set_errno (EAFNOSUPPORT);
+      connect_state (unconnected);
+      return -1;
+    }
+  if (sun.un_len == 3 && sun.un.sun_path[0] == '\0')
+    {
+      set_errno (EINVAL);
+      connect_state (unconnected);
+      return -1;
+    }
+  /* Check if peer address exists. */
+  RtlInitEmptyUnicodeString (&pipe_name, pipe_name_buf, sizeof pipe_name_buf);
+  if (open_file (&sun, peer_type, &pipe_name) < 0)
+    {
+      connect_state (unconnected);
+      return -1;
+    }
+  if (peer_type != get_socket_type ())
+    {
+      set_errno (EINVAL);
+      connect_state (unconnected);
+      return -1;
+    }
+  set_peer_sun_path (&sun);
+  if (get_socket_type () != SOCK_DGRAM)
+    {
+      if (connect_pipe (&pipe_name) < 0)
+       {
+         if (get_errno () != EINPROGRESS)
+           {
+             set_peer_sun_path (NULL);
+             connect_state (connect_failed);
+           }
+         return -1;
+       }
+    }
+  connect_state (connected);
+  return 0;
 }
 
 int
@@ -743,40 +1396,52 @@ fhandler_socket_unix::shutdown (int how)
 int
 fhandler_socket_unix::close ()
 {
+  HANDLE thr = InterlockedExchangePointer (&connect_wait_thr, NULL);
+  if (thr)
+    {
+      TerminateThread (thr, 0);
+      CloseHandle (thr);
+    }
+  PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
+  if (param)
+    cfree (param);
   if (get_handle ())
     NtClose (get_handle ());
-  if (file && file != INVALID_HANDLE_VALUE)
-    NtClose (file);
+  if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE)
+    NtClose (backing_file_handle);
   return 0;
 }
 
 int
 fhandler_socket_unix::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid)
 {
+  int ret = -1;
+
   if (get_socket_type () != SOCK_STREAM)
     {
       set_errno (EINVAL);
       return -1;
     }
+  AcquireSRWLockShared (&conn_lock);
   if (connect_state () != connected)
+    set_errno (ENOTCONN);
+  else
     {
-      set_errno (ENOTCONN);
-      return -1;
-    }
-
-  __try
-    {
-      if (pid)
-       *pid = peer_cred.pid;
-      if (euid)
-       *euid = peer_cred.uid;
-      if (egid)
-       *egid = peer_cred.gid;
-      return 0;
+      __try
+       {
+         if (pid)
+           *pid = peer_cred.pid;
+         if (euid)
+           *euid = peer_cred.uid;
+         if (egid)
+           *egid = peer_cred.gid;
+         ret = 0;
+       }
+      __except (EFAULT) {}
+      __endtry
     }
-  __except (EFAULT) {}
-  __endtry
-  return -1;
+  ReleaseSRWLockShared (&conn_lock);
+  return ret;
 }
 
 ssize_t
@@ -905,7 +1570,10 @@ fhandler_socket_unix::getsockopt (int level, int optname, 
const void *optval,
        case SO_ERROR:
          {
            int *e = (int *) optval;
-           *e = 0;
+           LONG err;
+
+           err = InterlockedExchange (&so_error, 0);
+           *e = err;
            break;
          }
 
@@ -1019,6 +1687,15 @@ fhandler_socket_unix::ioctl (unsigned int cmd, void *p)
     case _IOR('f', 127, int):
 #endif
     case FIONBIO:
+      {
+       const bool was_nonblocking = is_nonblocking ();
+       set_nonblocking (*(int *) p);
+       const bool now_nonblocking = is_nonblocking ();
+       if (was_nonblocking != now_nonblocking)
+         set_pipe_non_blocking (now_nonblocking);
+       ret = 0;
+       break;
+      }
     case SIOCATMARK:
       break;
     default:
@@ -1031,7 +1708,7 @@ fhandler_socket_unix::ioctl (unsigned int cmd, void *p)
 int
 fhandler_socket_unix::fcntl (int cmd, intptr_t arg)
 {
-  int ret = 0;
+  int ret = -1;
 
   switch (cmd)
     {
@@ -1039,6 +1716,20 @@ fhandler_socket_unix::fcntl (int cmd, intptr_t arg)
       break;
     case F_GETOWN:
       break;
+    case F_SETFL:
+      {
+       const bool was_nonblocking = is_nonblocking ();
+       const int allowed_flags = O_APPEND | O_NONBLOCK_MASK;
+       int new_flags = arg & allowed_flags;
+       if ((new_flags & OLD_O_NDELAY) && (new_flags & O_NONBLOCK))
+         new_flags &= ~OLD_O_NDELAY;
+       set_flags ((get_flags () & ~allowed_flags) | new_flags);
+       const bool now_nonblocking = is_nonblocking ();
+       if (was_nonblocking != now_nonblocking)
+         set_pipe_non_blocking (now_nonblocking);
+       ret = 0;
+       break;
+      }
     default:
       ret = fhandler_socket::fcntl (cmd, arg);
       break;
diff --git a/winsup/cygwin/globals.cc b/winsup/cygwin/globals.cc
index d94a4f8..9dac030 100644
--- a/winsup/cygwin/globals.cc
+++ b/winsup/cygwin/globals.cc
@@ -149,6 +149,8 @@ const int __collate_load_error = 0;
   extern UNICODE_STRING _RDATA ro_u_natdir = _ROU (L"Directory");
   extern UNICODE_STRING _RDATA ro_u_natsyml = _ROU (L"SymbolicLink");
   extern UNICODE_STRING _RDATA ro_u_natdev = _ROU (L"Device");
+  extern UNICODE_STRING _RDATA ro_u_npfs = _ROU (L"\\Device\\NamedPipe");
+  extern UNICODE_STRING _RDATA ro_u_npfs_dir = _ROU (L"\\Device\\NamedPipe\\");
   #undef _ROU
 
   /* This is an exported copy of environ which can be used by DLLs

Reply via email to