From 4cd57934be32395107960625c207854db949d7b7 Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Tue, 6 Mar 2018 18:55:03 +0100 Subject: [PATCH] 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 --- winsup/cygwin/fhandler.h | 56 +- winsup/cygwin/fhandler_socket_unix.cc | 783 ++++++++++++++++++++++++-- winsup/cygwin/globals.cc | 2 + 3 files changed, 775 insertions(+), 66 deletions(-) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index fc831798d..392ba6c4e 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 9ea9f45ed..ae422dda4 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; } - gen_pipe_name (); - pipe = create_pipe (); - if (!pipe) - return -1; - file = unnamed ? autobind (&sun) : create_file (&sun); - if (!file) + AcquireSRWLockExclusive (&bind_lock); + if (binding_state () == bind_pending) { - NtClose (pipe); + 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 (); + 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) + { + 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; + ret = 0; + } + __except (EFAULT) {} + __endtry } - - __try - { - if (pid) - *pid = peer_cred.pid; - if (euid) - *euid = peer_cred.uid; - if (egid) - *egid = peer_cred.gid; - return 0; - } - __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 d94a4f844..9dac030ff 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