diff options
Diffstat (limited to 'winsup/cygwin/fhandler/socket_unix.cc')
-rw-r--r-- | winsup/cygwin/fhandler/socket_unix.cc | 2406 |
1 files changed, 2406 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/socket_unix.cc b/winsup/cygwin/fhandler/socket_unix.cc new file mode 100644 index 000000000..0cd97f625 --- /dev/null +++ b/winsup/cygwin/fhandler/socket_unix.cc @@ -0,0 +1,2406 @@ +/* fhandler_socket_unix.cc. + + See fhandler.h for a description of the fhandler classes. + + This file is part of Cygwin. + + This software is a copyrighted work licensed under the terms of the + Cygwin license. Please consult the file "CYGWIN_LICENSE" for + details. */ + +#include "winsup.h" + +GUID __cygwin_socket_guid = { + .Data1 = 0xefc1714d, + .Data2 = 0x7b19, + .Data3 = 0x4407, + .Data4 = { 0xba, 0xb3, 0xc5, 0xb1, 0xf9, 0x2c, 0xb8, 0x8c } +}; + +#ifdef __WITH_AF_UNIX + +#include <w32api/winioctl.h> +#include <asm/byteorder.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/param.h> +#include <sys/statvfs.h> +#include <cygwin/acl.h> +#include "cygerrno.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "shared_info.h" +#include "ntdll.h" +#include "miscfuncs.h" +#include "tls_pbuf.h" + +/* + Abstract socket: + + An abstract socket is represented by a symlink in the native + NT namespace, within the Cygwin subdir in BasedNamedObjects. + So it's globally available but only exists as long as at least on + descriptor on the socket is open, as desired. + + The name of the symlink is: "af-unix-<sun_path>" + + <sun_path> is the transposed sun_path string, including the leading + NUL. The transposition is simplified in that it uses every byte + in the valid sun_path name as is, no extra multibyte conversion. + The content of the symlink is the basename of the underlying pipe. + + Named socket: + + A named socket is represented by a reparse point with a Cygwin-specific + tag and GUID. The GenericReparseBuffer content is the basename of the + underlying pipe. + + Pipe: + + The pipe is named \\.\pipe\cygwin-<installation_key>-unix-[sd]-<uniq_id> + + - <installation_key> is the 8 byte hex Cygwin installation key + - [sd] is s for SOCK_STREAM, d for SOCK_DGRAM + - <uniq_id> is an 8 byte hex unique number + + Note: We use MAX_PATH below for convenience where sufficient. It's + big enough to hold sun_paths as well as pipe names as well as packet + headers etc., 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 sends 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 sends its sun_path with each sendmsg/sendto. +*/ +class af_unix_pkt_hdr_t +{ + public: + uint16_t pckt_len; /* size of packet including header */ + bool admin_pkg : 1; /* admin packets are marked as such */ + shut_state shut_info : 2; /* _SHUT_RECV /_SHUT_SEND. */ + 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 */ + + af_unix_pkt_hdr_t (bool a, shut_state s, uint8_t n, uint16_t c, uint16_t d) + { init (a, s, n, c, d); } + void init (bool a, shut_state s, uint8_t n, uint16_t c, uint16_t d) + { + admin_pkg = a; + 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; \ + (struct cmsghdr *)(((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)); \ + }) + +/* Some error conditions on pipes have multiple status codes, unfortunately. */ +#define STATUS_PIPE_NO_INSTANCE_AVAILABLE(status) \ + ({ NTSTATUS _s = (status); \ + _s == STATUS_INSTANCE_NOT_AVAILABLE \ + || _s == STATUS_PIPE_NOT_AVAILABLE \ + || _s == STATUS_PIPE_BUSY; }) + +#define STATUS_PIPE_IS_CLOSING(status) \ + ({ NTSTATUS _s = (status); \ + _s == STATUS_PIPE_CLOSING \ + || _s == STATUS_PIPE_EMPTY; }) + +#define STATUS_PIPE_INVALID(status) \ + ({ NTSTATUS _s = (status); \ + _s == STATUS_INVALID_INFO_CLASS \ + || _s == STATUS_INVALID_PIPE_STATE \ + || _s == STATUS_INVALID_READ_MODE; }) + +#define STATUS_PIPE_MORE_DATA(status) \ + ({ NTSTATUS _s = (status); \ + _s == STATUS_BUFFER_OVERFLOW \ + || _s == STATUS_MORE_PROCESSING_REQUIRED; }) + +/* Default timeout value of connect: 20 secs, as on Linux. */ +#define AF_UNIX_CONNECT_TIMEOUT (-20 * NS100PERSEC) + +void +sun_name_t::set (const struct sockaddr_un *name, socklen_t namelen) +{ + if (namelen < 0) + namelen = 0; + un_len = namelen < (__socklen_t) sizeof un ? namelen : sizeof un; + un.sun_family = AF_UNIX; + if (name && un_len) + memcpy (&un, name, un_len); + _nul[un_len] = '\0'; +} + +static HANDLE +create_event () +{ + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + HANDLE evt = NULL; + + 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 evt; +} + +/* Called from socket, socketpair, accept4 */ +int +fhandler_socket_unix::create_shmem () +{ + HANDLE sect; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + LARGE_INTEGER size = { .QuadPart = sizeof (af_unix_shmem_t) }; + SIZE_T viewsize = sizeof (af_unix_shmem_t); + PVOID addr = NULL; + + InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL, NULL); + status = NtCreateSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, + &attr, &size, PAGE_READWRITE, SEC_COMMIT, NULL); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + status = NtMapViewOfSection (sect, NtCurrentProcess (), &addr, 0, viewsize, + NULL, &viewsize, ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + NtClose (sect); + __seterrno_from_nt_status (status); + return -1; + } + shmem_handle = sect; + shmem = (af_unix_shmem_t *) addr; + return 0; +} + +/* Called from dup, fixup_after_fork. Expects shmem_handle to be + valid. */ +int +fhandler_socket_unix::reopen_shmem () +{ + NTSTATUS status; + SIZE_T viewsize = PAGESIZE; + PVOID addr = NULL; + + status = NtMapViewOfSection (shmem_handle, NtCurrentProcess (), &addr, 0, + PAGESIZE, NULL, &viewsize, ViewShare, 0, + PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shmem = (af_unix_shmem_t *) addr; + return 0; +} + +/* Character length of pipe name, excluding trailing NUL. */ +#define CYGWIN_PIPE_SOCKET_NAME_LEN 47 + +/* Character position encoding the socket type in a pipe name. */ +#define CYGWIN_PIPE_SOCKET_TYPE_POS 29 + +void +fhandler_socket_unix::gen_pipe_name () +{ + WCHAR pipe_name_buf[CYGWIN_PIPE_SOCKET_NAME_LEN + 1]; + UNICODE_STRING pipe_name; + + __small_swprintf (pipe_name_buf, L"cygwin-%S-unix-%C-%016_X", + &cygheap->installation_key, + get_type_char (), + get_unique_id ()); + RtlInitUnicodeString (&pipe_name, pipe_name_buf); + pc.set_nt_native_path (&pipe_name); +} + +HANDLE +fhandler_socket_unix::create_abstract_link (const sun_name_t *sun, + PUNICODE_STRING pipe_name) +{ + WCHAR name[MAX_PATH]; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + UNICODE_STRING uname; + HANDLE fh = NULL; + + PWCHAR p = wcpcpy (name, L"af-unix-"); + /* NUL bytes have no special meaning in an abstract socket name, so + we assume iso-8859-1 for simplicity and transpose the string. + transform_chars_af_unix is doing just that. */ + p = transform_chars_af_unix (p, sun->un.sun_path, sun->un_len); + *p = L'\0'; + RtlInitUnicodeString (&uname, name); + InitializeObjectAttributes (&attr, &uname, OBJ_CASE_INSENSITIVE, + get_shared_parent_dir (), NULL); + /* Fill symlink with name of pipe */ + status = NtCreateSymbolicLinkObject (&fh, SYMBOLIC_LINK_ALL_ACCESS, + &attr, pipe_name); + if (!NT_SUCCESS (status)) + { + if (status == STATUS_OBJECT_NAME_EXISTS + || status == STATUS_OBJECT_NAME_COLLISION) + set_errno (EADDRINUSE); + else + __seterrno_from_nt_status (status); + } + return fh; +} + +struct rep_pipe_name_t +{ + USHORT Length; + WCHAR PipeName[1]; +}; + +HANDLE +fhandler_socket_unix::create_reparse_point (const sun_name_t *sun, + PUNICODE_STRING pipe_name) +{ + ULONG access; + HANDLE old_trans = NULL, trans = NULL; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + NTSTATUS status; + HANDLE fh = NULL; + PREPARSE_GUID_DATA_BUFFER rp; + rep_pipe_name_t *rep_pipe_name; + + const DWORD data_len = offsetof (rep_pipe_name_t, PipeName) + + pipe_name->Length + sizeof (WCHAR); + + path_conv pc (sun->un.sun_path, PC_SYM_FOLLOW); + if (pc.error) + { + set_errno (pc.error); + return NULL; + } + if (pc.exists ()) + { + set_errno (EADDRINUSE); + return NULL; + } + /* We will overwrite the DACL after the call to NtCreateFile. This + requires READ_CONTROL and WRITE_DAC access, otherwise get_file_sd + and set_file_sd both have to open the file again. + FIXME: On remote NTFS shares open sometimes fails because even the + creator of the file doesn't have the right to change the DACL. + I don't know what setting that is or how to recognize such a share, + so for now we don't request WRITE_DAC on remote drives. */ + access = DELETE | FILE_GENERIC_WRITE; + if (!pc.isremote ()) + access |= READ_CONTROL | WRITE_DAC | WRITE_OWNER; + if ((pc.fs_flags () & FILE_SUPPORTS_TRANSACTIONS)) + start_transaction (old_trans, trans); + +retry_after_transaction_error: + status = NtCreateFile (&fh, DELETE | FILE_GENERIC_WRITE, + pc.get_object_attr (attr, sec_none_nih), &io, + NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, + FILE_SYNCHRONOUS_IO_NONALERT + | FILE_NON_DIRECTORY_FILE + | FILE_OPEN_FOR_BACKUP_INTENT + | FILE_OPEN_REPARSE_POINT, + NULL, 0); + if (NT_TRANSACTIONAL_ERROR (status) && trans) + { + stop_transaction (status, old_trans, trans); + goto retry_after_transaction_error; + } + + if (!NT_SUCCESS (status)) + { + if (io.Information == FILE_EXISTS) + set_errno (EADDRINUSE); + else + __seterrno_from_nt_status (status); + goto out; + } + rp = (PREPARSE_GUID_DATA_BUFFER) + alloca (REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + data_len); + rp->ReparseTag = IO_REPARSE_TAG_CYGUNIX; + rp->ReparseDataLength = data_len; + rp->Reserved = 0; + memcpy (&rp->ReparseGuid, CYGWIN_SOCKET_GUID, sizeof (GUID)); + rep_pipe_name = (rep_pipe_name_t *) rp->GenericReparseBuffer.DataBuffer; + rep_pipe_name->Length = pipe_name->Length; + memcpy (rep_pipe_name->PipeName, pipe_name->Buffer, pipe_name->Length); + rep_pipe_name->PipeName[pipe_name->Length / sizeof (WCHAR)] = L'\0'; + status = NtFsControlFile (fh, NULL, NULL, NULL, &io, + FSCTL_SET_REPARSE_POINT, rp, + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + + rp->ReparseDataLength, NULL, 0); + if (NT_SUCCESS (status)) + { + mode_t perms = (S_IRWXU | S_IRWXG | S_IRWXO) & ~cygheap->umask; + set_created_file_access (fh, pc, perms); + NtClose (fh); + /* We don't have to keep the file open, but the caller needs to + get a value != NULL to know the file creation went fine. */ + fh = INVALID_HANDLE_VALUE; + } + else if (!trans) + { + FILE_DISPOSITION_INFORMATION fdi = { TRUE }; + + __seterrno_from_nt_status (status); + status = NtSetInformationFile (fh, &io, &fdi, sizeof fdi, + FileDispositionInformation); + if (!NT_SUCCESS (status)) + debug_printf ("Setting delete dispostion failed, status = %y", + status); + NtClose (fh); + fh = NULL; + } + +out: + if (trans) + stop_transaction (status, old_trans, trans); + return fh; +} + +HANDLE +fhandler_socket_unix::create_file (const sun_name_t *sun) +{ + if (sun->un_len <= (socklen_t) sizeof (sa_family_t) + || (sun->un_len == 3 && sun->un.sun_path[0] == '\0')) + { + set_errno (EINVAL); + return NULL; + } + if (sun->un.sun_path[0] == '\0') + return create_abstract_link (sun, pc.get_nt_native_path ()); + return create_reparse_point (sun, pc.get_nt_native_path ()); +} + +int +fhandler_socket_unix::open_abstract_link (sun_name_t *sun, + PUNICODE_STRING pipe_name) +{ + WCHAR name[MAX_PATH]; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + UNICODE_STRING uname; + HANDLE fh; + + PWCHAR p = wcpcpy (name, L"af-unix-"); + p = transform_chars_af_unix (p, sun->un.sun_path, sun->un_len); + *p = L'\0'; + RtlInitUnicodeString (&uname, name); + InitializeObjectAttributes (&attr, &uname, OBJ_CASE_INSENSITIVE, + get_shared_parent_dir (), NULL); + status = NtOpenSymbolicLinkObject (&fh, SYMBOLIC_LINK_QUERY, &attr); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + if (pipe_name) + status = NtQuerySymbolicLinkObject (fh, pipe_name, NULL); + NtClose (fh); + if (pipe_name) + { + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + /* Enforce NUL-terminated pipe name. */ + pipe_name->Buffer[pipe_name->Length / sizeof (WCHAR)] = L'\0'; + } + return 0; +} + +int +fhandler_socket_unix::open_reparse_point (sun_name_t *sun, + PUNICODE_STRING pipe_name) +{ + NTSTATUS status; + HANDLE fh; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + PREPARSE_GUID_DATA_BUFFER rp; + tmp_pathbuf tp; + + path_conv pc (sun->un.sun_path, PC_SYM_FOLLOW); + if (pc.error) + { + set_errno (pc.error); + return -1; + } + if (!pc.exists ()) + { + set_errno (ENOENT); + return -1; + } + pc.get_object_attr (attr, sec_none_nih); + do + { + status = NtOpenFile (&fh, FILE_GENERIC_READ, &attr, &io, + FILE_SHARE_VALID_FLAGS, + FILE_SYNCHRONOUS_IO_NONALERT + | FILE_NON_DIRECTORY_FILE + | FILE_OPEN_FOR_BACKUP_INTENT + | FILE_OPEN_REPARSE_POINT); + if (status == STATUS_SHARING_VIOLATION) + { + /* While we hope that the sharing violation is only temporary, we + also could easily get stuck here, waiting for a file in use by + some greedy Win32 application. Therefore we should never wait + endlessly without checking for signals and thread cancel event. */ + pthread_testcancel (); + if (cygwait (NULL, cw_nowait, cw_sig_eintr) == WAIT_SIGNALED + && !_my_tls.call_signal_handler ()) + { + set_errno (EINTR); + return -1; + } + yield (); + } + else if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + } + while (status == STATUS_SHARING_VIOLATION); + rp = (PREPARSE_GUID_DATA_BUFFER) tp.c_get (); + status = NtFsControlFile (fh, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, + NULL, 0, rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + NtClose (fh); + if (rp->ReparseTag == IO_REPARSE_TAG_CYGUNIX + && memcmp (CYGWIN_SOCKET_GUID, &rp->ReparseGuid, sizeof (GUID)) == 0) + { + if (pipe_name) + { + rep_pipe_name_t *rep_pipe_name = (rep_pipe_name_t *) + rp->GenericReparseBuffer.DataBuffer; + pipe_name->Length = rep_pipe_name->Length; + /* pipe name in reparse point is NUL-terminated */ + memcpy (pipe_name->Buffer, rep_pipe_name->PipeName, + rep_pipe_name->Length + sizeof (WCHAR)); + } + return 0; + } + return -1; +} + +int +fhandler_socket_unix::open_file (sun_name_t *sun, int &type, + PUNICODE_STRING pipe_name) +{ + int ret = -1; + + if (sun->un_len <= (socklen_t) sizeof (sa_family_t) + || (sun->un_len == 3 && sun->un.sun_path[0] == '\0')) + set_errno (EINVAL); + else if (sun->un.sun_path[0] == '\0') + ret = open_abstract_link (sun, pipe_name); + else + ret = open_reparse_point (sun, pipe_name); + if (!ret) + switch (pipe_name->Buffer[CYGWIN_PIPE_SOCKET_TYPE_POS]) + { + case 'd': + type = SOCK_DGRAM; + break; + case 's': + type = SOCK_STREAM; + break; + default: + set_errno (EINVAL); + ret = -1; + break; + } + return ret; +} + +HANDLE +fhandler_socket_unix::autobind (sun_name_t* sun) +{ + uint32_t id; + HANDLE fh; + + do + { + /* Use only 5 hex digits (up to 2^20 sockets) for Linux compat */ + set_unique_id (); + id = get_unique_id () & 0xfffff; + sun->un.sun_path[0] = '\0'; + sun->un_len = sizeof (sa_family_t) + + 1 /* leading NUL */ + + __small_sprintf (sun->un.sun_path + 1, "%5X", id); + } + while ((fh = create_abstract_link (sun, pc.get_nt_native_path ())) == NULL); + return fh; +} + +wchar_t +fhandler_socket_unix::get_type_char () +{ + switch (get_socket_type ()) + { + case SOCK_STREAM: + return 's'; + case SOCK_DGRAM: + return 'd'; + default: + return '?'; + } +} + +/* This also sets the pipe to message mode unconditionally. */ +void +fhandler_socket_unix::set_pipe_non_blocking (bool nonblocking) +{ + if (get_handle ()) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + FILE_PIPE_INFORMATION fpi; + + fpi.ReadMode = FILE_PIPE_MESSAGE_MODE; + fpi.CompletionMode = nonblocking ? FILE_PIPE_COMPLETE_OPERATION + : FILE_PIPE_QUEUE_OPERATION; + status = NtSetInformationFile (get_handle (), &io, &fpi, sizeof fpi, + FilePipeInformation); + if (!NT_SUCCESS (status)) + debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status); + } +} + +/* Apart from being called from bind(), from_bind indicates that the caller + already locked state_lock, so send_sock_info doesn't lock, only unlocks + state_lock. */ +int +fhandler_socket_unix::send_sock_info (bool from_bind) +{ + sun_name_t *sun; + size_t plen; + size_t clen = 0; + af_unix_pkt_hdr_t *packet; + NTSTATUS status; + IO_STATUS_BLOCK io; + + if (!from_bind) + { + state_lock (); + /* When called from connect, initialize credentials. accept4 already + did it (copied from listening socket). */ + if (sock_cred ()->pid == 0) + set_cred (); + } + sun = sun_path (); + plen = sizeof *packet + sun->un_len; + /* When called from connect/accept4, send SCM_CREDENTIALS, too. */ + if (!from_bind) + { + clen = CMSG_SPACE (sizeof (struct ucred)); + plen += clen; + } + packet = (af_unix_pkt_hdr_t *) alloca (plen); + packet->init (true, _SHUT_NONE, sun->un_len, clen, 0); + if (sun) + memcpy (AF_UNIX_PKT_NAME (packet), &sun->un, sun->un_len); + if (!from_bind) + { + struct cmsghdr *cmsg = AF_UNIX_PKT_CMSG (packet); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN (sizeof (struct ucred)); + memcpy (CMSG_DATA(cmsg), sock_cred (), sizeof (struct ucred)); + } + + state_unlock (); + + /* The theory: Fire and forget. */ + 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 ()); + io_unlock (); + if (!NT_SUCCESS (status)) + { + debug_printf ("Couldn't send my name: NtWriteFile: %y", status); + return -1; + } + return 0; +} + +/* Checks if the next packet in the pipe is an administrative packet. + If so, it reads it from the pipe, handles it. Returns an error code. */ +int +fhandler_socket_unix::grab_admin_pkg () +{ + HANDLE evt; + NTSTATUS status; + IO_STATUS_BLOCK io; + /* MAX_PATH is more than sufficient for admin packets. */ + PFILE_PIPE_PEEK_BUFFER pbuf = (PFILE_PIPE_PEEK_BUFFER) alloca (MAX_PATH); + if (!(evt = create_event ())) + return 0; + io_lock (); + ULONG ret_len = peek_pipe (pbuf, MAX_PATH, evt); + if (pbuf->NumberOfMessages == 0 || ret_len < sizeof (af_unix_pkt_hdr_t)) + { + io_unlock (); + NtClose (evt); + return 0; + } + af_unix_pkt_hdr_t *packet = (af_unix_pkt_hdr_t *) pbuf->Data; + if (!packet->admin_pkg) + io_unlock (); + else + { + packet = (af_unix_pkt_hdr_t *) pbuf; + status = NtReadFile (get_handle (), evt, NULL, NULL, &io, packet, + MAX_PATH, NULL, NULL); + if (status == STATUS_PENDING) + { + /* Very short-lived */ + status = NtWaitForSingleObject (evt, FALSE, NULL); + if (NT_SUCCESS (status)) + status = io.Status; + } + io_unlock (); + if (NT_SUCCESS (status)) + { + state_lock (); + if (packet->shut_info) + { + /* Peer's shutdown sends the SHUT flags as used by the peer. + They have to be reversed for our side. */ + int shut_info = saw_shutdown (); + if (packet->shut_info & _SHUT_RECV) + shut_info |= _SHUT_SEND; + if (packet->shut_info & _SHUT_SEND) + shut_info |= _SHUT_RECV; + saw_shutdown (shut_info); + /* FIXME: anything else here? */ + } + if (packet->name_len > 0) + peer_sun_path (AF_UNIX_PKT_NAME (packet), packet->name_len); + if (packet->cmsg_len > 0) + { + struct cmsghdr *cmsg = (struct cmsghdr *) + alloca (packet->cmsg_len); + memcpy (cmsg, AF_UNIX_PKT_CMSG (packet), packet->cmsg_len); + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_CREDENTIALS) + peer_cred ((struct ucred *) CMSG_DATA(cmsg)); + } + state_unlock (); + } + } + NtClose (evt); + return 0; +} + +/* Returns an error code. Locking is not required when called from accept4, + user space doesn't know about this socket yet. */ +int +fhandler_socket_unix::recv_peer_info () +{ + HANDLE evt; + NTSTATUS status; + IO_STATUS_BLOCK io; + af_unix_pkt_hdr_t *packet; + struct sockaddr_un *un; + ULONG len; + int ret = 0; + + if (!(evt = create_event ())) + return ENOBUFS; + len = sizeof *packet + sizeof *un + CMSG_SPACE (sizeof (struct ucred)); + packet = (af_unix_pkt_hdr_t *) alloca (len); + set_pipe_non_blocking (false); + status = NtReadFile (get_handle (), evt, NULL, NULL, &io, packet, len, + NULL, NULL); + if (status == STATUS_PENDING) + { + DWORD ret; + LARGE_INTEGER timeout; + + timeout.QuadPart = AF_UNIX_CONNECT_TIMEOUT; + ret = cygwait (evt, &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; + } + } + set_pipe_non_blocking (is_nonblocking ()); + NtClose (evt); + if (!NT_SUCCESS (status) && ret == 0) + ret = geterrno_from_nt_status (status); + if (ret == 0) + { + if (packet->name_len > 0) + peer_sun_path (AF_UNIX_PKT_NAME (packet), packet->name_len); + if (packet->cmsg_len > 0) + { + struct cmsghdr *cmsg = (struct cmsghdr *) alloca (packet->cmsg_len); + memcpy (cmsg, AF_UNIX_PKT_CMSG (packet), packet->cmsg_len); + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_CREDENTIALS) + peer_cred ((struct ucred *) CMSG_DATA(cmsg)); + } + } + return ret; +} + +HANDLE +fhandler_socket_unix::create_pipe (bool single_instance) +{ + NTSTATUS status; + HANDLE npfsh; + HANDLE ph; + ACCESS_MASK access; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + ULONG sharing; + ULONG nonblocking; + ULONG max_instances; + LARGE_INTEGER timeout; + + status = npfs_handle (npfsh); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return NULL; + } + access = GENERIC_READ | FILE_READ_ATTRIBUTES + | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES + | SYNCHRONIZE; + sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; + InitializeObjectAttributes (&attr, pc.get_nt_native_path (), + OBJ_INHERIT | OBJ_CASE_INSENSITIVE, + npfsh, NULL); + nonblocking = is_nonblocking () ? FILE_PIPE_COMPLETE_OPERATION + : FILE_PIPE_QUEUE_OPERATION; + max_instances = single_instance ? 1 : -1; + timeout.QuadPart = -500000; + status = NtCreateNamedPipeFile (&ph, access, &attr, &io, sharing, + FILE_CREATE, 0, + FILE_PIPE_MESSAGE_TYPE, + FILE_PIPE_MESSAGE_MODE, + nonblocking, max_instances, + rmem (), wmem (), &timeout); + if (!NT_SUCCESS (status)) + __seterrno_from_nt_status (status); + return ph; +} + +HANDLE +fhandler_socket_unix::create_pipe_instance () +{ + NTSTATUS status; + HANDLE npfsh; + HANDLE ph; + ACCESS_MASK access; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + ULONG sharing; + ULONG nonblocking; + ULONG max_instances; + LARGE_INTEGER timeout; + + status = npfs_handle (npfsh); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return NULL; + } + access = GENERIC_READ | FILE_READ_ATTRIBUTES + | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES + | SYNCHRONIZE; + sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; + /* NPFS doesn't understand reopening by handle, unfortunately. */ + InitializeObjectAttributes (&attr, pc.get_nt_native_path (), OBJ_INHERIT, + npfsh, NULL); + nonblocking = is_nonblocking () ? FILE_PIPE_COMPLETE_OPERATION + : FILE_PIPE_QUEUE_OPERATION; + max_instances = (get_socket_type () == SOCK_DGRAM) ? 1 : -1; + timeout.QuadPart = -500000; + status = NtCreateNamedPipeFile (&ph, access, &attr, &io, sharing, + FILE_OPEN, 0, + FILE_PIPE_MESSAGE_TYPE, + FILE_PIPE_MESSAGE_MODE, + nonblocking, max_instances, + rmem (), wmem (), &timeout); + if (!NT_SUCCESS (status)) + __seterrno_from_nt_status (status); + return ph; +} + +NTSTATUS +fhandler_socket_unix::open_pipe (PUNICODE_STRING pipe_name, bool xchg_sock_info) +{ + NTSTATUS status; + HANDLE npfsh; + ACCESS_MASK access; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + ULONG sharing; + HANDLE ph = NULL; + + status = npfs_handle (npfsh); + 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_handle (ph); + if (xchg_sock_info) + { + /* FIXME: Should we check for errors? */ + send_sock_info (false); + recv_peer_info (); + } + } + return status; +} + +struct conn_wait_info_t +{ + 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 +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; + HANDLE thr, evt; + PVOID param; + + if (!(cwt_termination_evt = create_event ())) + return -1; + wait_info = (conn_wait_info_t *) cmalloc (HEAP_3_FHANDLER, sizeof *wait_info); + if (!wait_info) + return -1; + 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 (); + goto out; + } + if (is_nonblocking ()) + { + set_errno (EINPROGRESS); + goto out; + } + + waitret = cygwait (connect_wait_thr, cw_infinite, cw_cancel | cw_sig_eintr); + if (waitret == WAIT_OBJECT_0) + GetExitCodeThread (connect_wait_thr, &err); + else + { + SetEvent (cwt_termination_evt); + NtWaitForSingleObject (connect_wait_thr, FALSE, NULL); + GetExitCodeThread (connect_wait_thr, &err); + waitret = WAIT_SIGNALED; + } + thr = InterlockedExchangePointer (&connect_wait_thr, NULL); + if (thr) + NtClose (thr); + 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: + so_error (err); + if (err) + set_errno (err); + else + ret = 0; + break; + } +out: + evt = InterlockedExchangePointer (&cwt_termination_evt, NULL); + if (evt) + NtClose (evt); + return ret; +} + +int +fhandler_socket_unix::connect_pipe (PUNICODE_STRING pipe_name) +{ + NTSTATUS status; + + /* Try connecting first. If it doesn't work, wait for the pipe + to become available. */ + status = open_pipe (pipe_name, get_socket_type () != SOCK_DGRAM); + if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status)) + return wait_pipe (pipe_name); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + so_error (get_errno ()); + return -1; + } + so_error (0); + return 0; +} + +int +fhandler_socket_unix::listen_pipe () +{ + NTSTATUS status; + IO_STATUS_BLOCK io; + HANDLE evt = NULL; + DWORD waitret = WAIT_OBJECT_0; + int ret = -1; + + io.Status = STATUS_PENDING; + if (!is_nonblocking () && !(evt = create_event ())) + return -1; + status = NtFsControlFile (get_handle (), evt, NULL, NULL, &io, + FSCTL_PIPE_LISTEN, NULL, 0, NULL, 0); + if (status == STATUS_PENDING) + { + waitret = cygwait (evt ?: get_handle (), cw_infinite, + cw_cancel | cw_sig_eintr); + if (waitret == WAIT_OBJECT_0) + status = io.Status; + } + if (evt) + NtClose (evt); + if (waitret == WAIT_CANCELED) + pthread::static_cancel_self (); + else if (waitret == WAIT_SIGNALED) + set_errno (EINTR); + else if (status == STATUS_PIPE_LISTENING) + set_errno (EAGAIN); + else if (status == STATUS_SUCCESS || status == STATUS_PIPE_CONNECTED) + ret = 0; + else + __seterrno_from_nt_status (status); + return ret; +} + +ULONG +fhandler_socket_unix::peek_pipe (PFILE_PIPE_PEEK_BUFFER pbuf, ULONG psize, + HANDLE evt) +{ + NTSTATUS status; + IO_STATUS_BLOCK io; + + status = NtFsControlFile (get_handle (), evt, NULL, NULL, &io, + FSCTL_PIPE_PEEK, NULL, 0, pbuf, psize); + if (status == STATUS_PENDING) + { + /* Very short-lived */ + status = NtWaitForSingleObject (evt ?: get_handle (), FALSE, NULL); + if (NT_SUCCESS (status)) + status = io.Status; + } + return NT_SUCCESS (status) ? (io.Information + - offsetof (FILE_PIPE_PEEK_BUFFER, Data)) + : 0; +} + +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); + /* Short-lived. Don't use cygwait. We don't want to be interrupted. */ + if (status == STATUS_PENDING + && NtWaitForSingleObject (ph, FALSE, NULL) == WAIT_OBJECT_0) + status = io.Status; + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + return 0; +} + +void +fhandler_socket_unix::init_cred () +{ + struct ucred *scred = shmem->sock_cred (); + struct ucred *pcred = shmem->peer_cred (); + scred->pid = pcred->pid = (pid_t) 0; + scred->uid = pcred->uid = (uid_t) -1; + scred->gid = pcred->gid = (gid_t) -1; +} + +void +fhandler_socket_unix::set_cred () +{ + struct ucred *scred = shmem->sock_cred (); + scred->pid = myself->pid; + scred->uid = myself->uid; + scred->gid = myself->gid; +} + +void +fhandler_socket_unix::fixup_helper () +{ + if (shmem_handle) + reopen_shmem (); + connect_wait_thr = NULL; + cwt_termination_evt = NULL; + cwt_param = NULL; +} + +/* ========================== public methods ========================= */ + +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"); + if (shmem_handle) + fork_fixup (parent, shmem_handle, "shmem_handle"); + fixup_helper (); +} + +void +fhandler_socket_unix::fixup_after_exec () +{ + if (!close_on_exec ()) + fixup_helper (); +} + +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); + if (shmem_handle) + set_no_inheritance (shmem_handle, val); +} + +fhandler_socket_unix::fhandler_socket_unix () +{ +} + +fhandler_socket_unix::~fhandler_socket_unix () +{ +} + +int +fhandler_socket_unix::dup (fhandler_base *child, int flags) +{ + if (get_flags () & O_PATH) + /* We're viewing the socket as a disk file, but fhandler_base::dup + suffices here. */ + return fhandler_base::dup (child, flags); + + if (fhandler_socket::dup (child, flags)) + { + __seterrno (); + return -1; + } + fhandler_socket_unix *fhs = (fhandler_socket_unix *) child; + if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE + && !DuplicateHandle (GetCurrentProcess (), backing_file_handle, + GetCurrentProcess (), &fhs->backing_file_handle, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + fhs->close (); + return -1; + } + if (!DuplicateHandle (GetCurrentProcess (), shmem_handle, + GetCurrentProcess (), &fhs->shmem_handle, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + fhs->close (); + return -1; + } + if (fhs->reopen_shmem () < 0) + { + __seterrno (); + fhs->close (); + return -1; + } + fhs->sun_path (sun_path ()); + fhs->peer_sun_path (peer_sun_path ()); + fhs->connect_wait_thr = NULL; + fhs->cwt_termination_evt = NULL; + fhs->cwt_param = NULL; + return 0; +} + +/* 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 + - Set connect_state + - Set so_error for later call to select +*/ +DWORD +fhandler_socket_unix::wait_pipe_thread (PUNICODE_STRING pipe_name) +{ + HANDLE npfsh; + HANDLE evt; + LONG error = 0; + NTSTATUS status; + IO_STATUS_BLOCK io; + ULONG pwbuf_size; + PFILE_PIPE_WAIT_FOR_BUFFER pwbuf; + LONGLONG stamp; + + status = npfs_handle (npfsh); + if (!NT_SUCCESS (status)) + { + error = geterrno_from_nt_status (status); + goto out; + } + if (!(evt = create_event ())) + 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 = AF_UNIX_CONNECT_TIMEOUT; + pwbuf->NameLength = pipe_name->Length; + pwbuf->TimeoutSpecified = TRUE; + memcpy (pwbuf->Name, pipe_name->Buffer, pipe_name->Length); + stamp = get_clock (CLOCK_MONOTONIC)->n100secs (); + do + { + status = NtFsControlFile (npfsh, evt, NULL, NULL, &io, FSCTL_PIPE_WAIT, + pwbuf, pwbuf_size, NULL, 0); + if (status == STATUS_PENDING) + { + HANDLE w[2] = { evt, cwt_termination_evt }; + switch (WaitForMultipleObjects (2, w, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + status = io.Status; + break; + case WAIT_OBJECT_0 + 1: + default: + status = STATUS_THREAD_IS_TERMINATING; + break; + } + } + switch (status) + { + case STATUS_SUCCESS: + { + status = open_pipe (pipe_name, get_socket_type () != SOCK_DGRAM); + if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status)) + { + /* 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 - get_clock (CLOCK_MONOTONIC)->n100secs (); + 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_THREAD_IS_TERMINATING: + error = EINTR; + break; + case STATUS_INVALID_DEVICE_REQUEST: + default: + error = EIO; + break; + } + } + while (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status)); +out: + PVOID param = InterlockedExchangePointer (&cwt_param, NULL); + if (param) + cfree (param); + conn_lock (); + state_lock (); + so_error (error); + connect_state (error ? connect_failed : connected); + state_unlock (); + conn_unlock (); + return error; +} + +int +fhandler_socket_unix::socket (int af, int type, int protocol, int flags) +{ + if (type != SOCK_STREAM && type != SOCK_DGRAM) + { + set_errno (EINVAL); + return -1; + } + if (protocol != 0) + { + set_errno (EPROTONOSUPPORT); + return -1; + } + if (create_shmem () < 0) + return -1; + rmem (262144); + wmem (262144); + set_addr_family (AF_UNIX); + set_socket_type (type); + set_flags (O_RDWR | O_BINARY); + if (flags & SOCK_NONBLOCK) + set_nonblocking (true); + if (flags & SOCK_CLOEXEC) + set_close_on_exec (true); + init_cred (); + set_handle (NULL); + set_unique_id (); + set_ino (get_unique_id ()); + return 0; +} + +int +fhandler_socket_unix::socketpair (int af, int type, int protocol, int flags, + fhandler_socket *fh_out) +{ + HANDLE pipe; + sun_name_t sun; + fhandler_socket_unix *fh = (fhandler_socket_unix *) fh_out; + + if (type != SOCK_STREAM && type != SOCK_DGRAM) + { + set_errno (EINVAL); + return -1; + } + if (protocol != 0) + { + set_errno (EPROTONOSUPPORT); + return -1; + } + + if (create_shmem () < 0) + return -1; + if (fh->create_shmem () < 0) + goto fh_shmem_failed; + /* socket() on both sockets */ + rmem (262144); + fh->rmem (262144); + wmem (262144); + fh->wmem (262144); + set_addr_family (AF_UNIX); + fh->set_addr_family (AF_UNIX); + set_socket_type (type); + fh->set_socket_type (type); + set_cred (); + fh->set_cred (); + set_unique_id (); + set_ino (get_unique_id ()); + /* bind/listen 1st socket */ + gen_pipe_name (); + pipe = create_pipe (true); + if (!pipe) + goto create_pipe_failed; + set_handle (pipe); + sun_path (&sun); + fh->peer_sun_path (&sun); + connect_state (listener); + /* connect 2nd socket, even for DGRAM. There's no difference as far + as socketpairs are concerned. */ + if (fh->open_pipe (pc.get_nt_native_path (), false) < 0) + goto fh_open_pipe_failed; + fh->connect_state (connected); + if (flags & SOCK_NONBLOCK) + { + set_nonblocking (true); + fh->set_nonblocking (true); + } + if (flags & SOCK_CLOEXEC) + { + set_close_on_exec (true); + fh->set_close_on_exec (true); + } + return 0; + +fh_open_pipe_failed: + NtClose (pipe); +create_pipe_failed: + NtUnmapViewOfSection (NtCurrentProcess (), fh->shmem); + NtClose (fh->shmem_handle); +fh_shmem_failed: + NtUnmapViewOfSection (NtCurrentProcess (), shmem); + NtClose (shmem_handle); + 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) +{ + sun_name_t sun (name, namelen); + bool unnamed = (sun.un_len == sizeof sun.un.sun_family); + HANDLE pipe = NULL; + + if (sun.un.sun_family != AF_UNIX) + { + set_errno (EINVAL); + return -1; + } + bind_lock (); + if (binding_state () == bind_pending) + { + set_errno (EALREADY); + bind_unlock (); + return -1; + } + if (binding_state () == bound) + { + set_errno (EINVAL); + bind_unlock (); + return -1; + } + binding_state (bind_pending); + bind_unlock (); + gen_pipe_name (); + if (get_socket_type () == SOCK_DGRAM) + { + pipe = create_pipe (true); + if (!pipe) + { + binding_state (unbound); + return -1; + } + set_handle (pipe); + } + backing_file_handle = unnamed ? autobind (&sun) : create_file (&sun); + if (!backing_file_handle) + { + set_handle (NULL); + if (pipe) + NtClose (pipe); + binding_state (unbound); + return -1; + } + state_lock (); + sun_path (&sun); + /* If we're already connected, send socket info to peer. In this case + send_sock_info calls state_unlock */ + if (connect_state () == connected) + send_sock_info (true); + else + state_unlock (); + binding_state (bound); + return 0; +} + +/* Create pipe on non-DGRAM sockets and set conn_state to listener. */ +int +fhandler_socket_unix::listen (int backlog) +{ + if (get_socket_type () == SOCK_DGRAM) + { + set_errno (EOPNOTSUPP); + return -1; + } + bind_lock (); + while (binding_state () == bind_pending) + yield (); + if (binding_state () == unbound) + { + set_errno (EDESTADDRREQ); + bind_unlock (); + return -1; + } + bind_unlock (); + conn_lock (); + if (connect_state () != unconnected && connect_state () != connect_failed) + { + set_errno (connect_state () == listener ? EADDRINUSE : EINVAL); + conn_unlock (); + return -1; + } + HANDLE pipe = create_pipe (false); + if (!pipe) + { + connect_state (unconnected); + return -1; + } + set_handle (pipe); + state_lock (); + set_cred (); + state_unlock (); + connect_state (listener); + conn_unlock (); + return 0; +} + +int +fhandler_socket_unix::accept4 (struct sockaddr *peer, int *len, int flags) +{ + 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. */ + io_lock (); + HANDLE accepted = get_handle (); + HANDLE new_inst = create_pipe_instance (); + int error = ENOBUFS; + if (!new_inst) + io_unlock (); + else + { + /* Set new io handle. */ + set_handle (new_inst); + io_unlock (); + /* Prepare new file descriptor. */ + cygheap_fdnew fd; + + if (fd >= 0) + { + fhandler_socket_unix *sock = (fhandler_socket_unix *) + build_fh_dev (dev ()); + if (sock) + { + if (sock->create_shmem () < 0) + goto create_shmem_failed; + + sock->set_addr_family (AF_UNIX); + 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_handle (accepted); + + sock->sun_path (sun_path ()); + sock->sock_cred (sock_cred ()); + /* Send this socket info to connecting socket. */ + sock->send_sock_info (false); + /* Fetch the packet sent by send_sock_info called by + connecting peer. */ + error = sock->recv_peer_info (); + if (error == 0) + { + __try + { + if (peer) + { + sun_name_t *sun = sock->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; + } + __except (NO_ERROR) + { + error = EFAULT; + } + __endtry + } +create_shmem_failed: + delete sock; + } + } + } + /* 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) +{ + 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. */ + conn_lock (); + if (connect_state () == connect_pending) + { + set_errno (EALREADY); + conn_unlock (); + return -1; + } + if (connect_state () == listener) + { + set_errno (EADDRINUSE); + conn_unlock (); + return -1; + } + if (connect_state () == connected && get_socket_type () != SOCK_DGRAM) + { + set_errno (EISCONN); + conn_unlock (); + return -1; + } + if (name->sa_family == AF_UNSPEC && get_socket_type () == SOCK_DGRAM) + { + connect_state (unconnected); + peer_sun_path (NULL); + conn_unlock (); + return 0; + } + connect_state (connect_pending); + conn_unlock (); + /* 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; + } + peer_sun_path (&sun); + if (get_socket_type () != SOCK_DGRAM) + { + if (connect_pipe (&pipe_name) < 0) + { + if (get_errno () != EINPROGRESS) + { + peer_sun_path (NULL); + connect_state (connect_failed); + } + return -1; + } + } + connect_state (connected); + return 0; +} + +int +fhandler_socket_unix::getsockname (struct sockaddr *name, int *namelen) +{ + sun_name_t *sun = sun_path (); + + memcpy (name, sun, MIN (*namelen, sun->un_len)); + *namelen = sun->un_len; + return 0; +} + +int +fhandler_socket_unix::getpeername (struct sockaddr *name, int *namelen) +{ + sun_name_t *sun = peer_sun_path (); + memcpy (name, sun, MIN (*namelen, sun->un_len)); + *namelen = sun->un_len; + return 0; +} + +int +fhandler_socket_unix::shutdown (int how) +{ + NTSTATUS status = STATUS_SUCCESS; + IO_STATUS_BLOCK io; + + if (how < SHUT_RD || how > SHUT_RDWR) + { + set_errno (EINVAL); + return -1; + } + /* Convert SHUT_RD/SHUT_WR/SHUT_RDWR to _SHUT_RECV/_SHUT_SEND bits. */ + ++how; + state_lock (); + int old_shutdown_mask = saw_shutdown (); + int new_shutdown_mask = old_shutdown_mask | how; + if (new_shutdown_mask != old_shutdown_mask) + saw_shutdown (new_shutdown_mask); + state_unlock (); + if (new_shutdown_mask != old_shutdown_mask) + { + /* Send shutdown info to peer. Note that it's not necessarily fatal + if the info isn't sent here. The info will be reproduced by any + followup package sent to the peer. */ + af_unix_pkt_hdr_t packet (true, (shut_state) new_shutdown_mask, 0, 0, 0); + 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 ()); + io_unlock (); + } + if (!NT_SUCCESS (status)) + { + debug_printf ("Couldn't send shutdown info: NtWriteFile: %y", status); + return -1; + } + return 0; +} + +int +fhandler_socket_unix::open (int flags, mode_t mode) +{ + /* We don't support opening sockets unless O_PATH is specified. */ + if (flags & O_PATH) + return open_fs (flags, mode); + + set_errno (EOPNOTSUPP); + return 0; +} + +int +fhandler_socket_unix::close () +{ + if (get_flags () & O_PATH) + return fhandler_base::close (); + + HANDLE evt = InterlockedExchangePointer (&cwt_termination_evt, NULL); + HANDLE thr = InterlockedExchangePointer (&connect_wait_thr, NULL); + if (thr) + { + if (evt) + SetEvent (evt); + NtWaitForSingleObject (thr, FALSE, NULL); + NtClose (thr); + } + if (evt) + NtClose (evt); + PVOID param = InterlockedExchangePointer (&cwt_param, NULL); + if (param) + cfree (param); + HANDLE hdl = InterlockedExchangePointer (&get_handle (), NULL); + if (hdl) + NtClose (hdl); + if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE) + NtClose (backing_file_handle); + HANDLE shm = InterlockedExchangePointer (&shmem_handle, NULL); + if (shm) + NtClose (shm); + param = InterlockedExchangePointer ((PVOID *) &shmem, NULL); + if (param) + NtUnmapViewOfSection (NtCurrentProcess (), param); + 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; + } + if (connect_state () != connected) + set_errno (ENOTCONN); + else + { + __try + { + state_lock (); + struct ucred *pcred = peer_cred (); + if (pid) + *pid = pcred->pid; + if (euid) + *euid = pcred->uid; + if (egid) + *egid = pcred->gid; + state_unlock (); + ret = 0; + } + __except (EFAULT) {} + __endtry + } + return ret; +} + +ssize_t +fhandler_socket_unix::recvmsg (struct msghdr *msg, int flags) +{ + set_errno (EAFNOSUPPORT); + return -1; +} + +ssize_t +fhandler_socket_unix::recvfrom (void *ptr, size_t len, int flags, + struct sockaddr *from, int *fromlen) +{ + struct iovec iov; + struct msghdr msg; + ssize_t ret; + + iov.iov_base = ptr; + iov.iov_len = len; + msg.msg_name = from; + msg.msg_namelen = from && fromlen ? *fromlen : 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ret = recvmsg (&msg, flags); + if (ret >= 0 && from && fromlen) + *fromlen = msg.msg_namelen; + return ret; +} + +void +fhandler_socket_unix::read (void *ptr, size_t& len) +{ + set_errno (EAFNOSUPPORT); + len = 0; + struct iovec iov; + struct msghdr msg; + + iov.iov_base = ptr; + iov.iov_len = len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + len = recvmsg (&msg, 0); +} + +ssize_t +fhandler_socket_unix::readv (const struct iovec *const iov, int iovcnt, + ssize_t tot) +{ + struct msghdr msg; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = (struct iovec *) iov; + msg.msg_iovlen = iovcnt; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + return recvmsg (&msg, 0); +} + +ssize_t +fhandler_socket_unix::sendmsg (const struct msghdr *msg, int flags) +{ + set_errno (EAFNOSUPPORT); + return -1; +} + +ssize_t +fhandler_socket_unix::sendto (const void *in_ptr, size_t len, int flags, + const struct sockaddr *to, int tolen) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = (void *) in_ptr; + iov.iov_len = len; + msg.msg_name = (void *) to; + msg.msg_namelen = to ? tolen : 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + return sendmsg (&msg, flags); +} + +ssize_t +fhandler_socket_unix::write (const void *ptr, size_t len) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = (void *) ptr; + iov.iov_len = len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + return sendmsg (&msg, 0); +} + +ssize_t +fhandler_socket_unix::writev (const struct iovec *const iov, int iovcnt, + ssize_t tot) +{ + struct msghdr msg; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = (struct iovec *) iov; + msg.msg_iovlen = iovcnt; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + return sendmsg (&msg, 0); +} + +int +fhandler_socket_unix::setsockopt (int level, int optname, const void *optval, + socklen_t optlen) +{ + /* Preprocessing setsockopt. */ + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_PASSCRED: + if (optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + + bool val; + val = !!*(int *) optval; + /* Using bind_lock here to make sure the autobind below is + covered. This is the only place to set so_passcred anyway. */ + bind_lock (); + if (val && binding_state () == unbound) + { + sun_name_t sun; + + binding_state (bind_pending); + backing_file_handle = autobind (&sun); + if (!backing_file_handle) + { + binding_state (unbound); + bind_unlock (); + return -1; + } + sun_path (&sun); + binding_state (bound); + } + so_passcred (val); + bind_unlock (); + break; + + case SO_REUSEADDR: + if (optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + reuseaddr (!!*(int *) optval); + break; + + case SO_RCVBUF: + if (optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + rmem (*(int *) optval); + break; + + case SO_SNDBUF: + if (optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + wmem (*(int *) optval); + break; + + case SO_RCVTIMEO: + case SO_SNDTIMEO: + if (optlen < (socklen_t) sizeof (struct timeval)) + { + set_errno (EINVAL); + return -1; + } + if (!timeval_to_ms ((struct timeval *) optval, + (optname == SO_RCVTIMEO) ? rcvtimeo () + : sndtimeo ())) + { + set_errno (EDOM); + return -1; + } + break; + + default: + /* AF_UNIX sockets simply ignore all other SOL_SOCKET options. */ + break; + } + break; + + default: + set_errno (ENOPROTOOPT); + return -1; + } + + return 0; +} + +int +fhandler_socket_unix::getsockopt (int level, int optname, const void *optval, + socklen_t *optlen) +{ + /* Preprocessing getsockopt.*/ + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_ERROR: + { + if (*optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + + int *e = (int *) optval; + LONG err; + + err = so_error (0); + *e = err; + break; + } + + case SO_PASSCRED: + { + if (*optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + + int *e = (int *) optval; + *e = so_passcred (); + break; + } + + case SO_PEERCRED: + { + struct ucred *cred = (struct ucred *) optval; + + if (*optlen < (socklen_t) sizeof *cred) + { + set_errno (EINVAL); + return -1; + } + int ret = getpeereid (&cred->pid, &cred->uid, &cred->gid); + if (!ret) + *optlen = (socklen_t) sizeof *cred; + return ret; + } + + case SO_REUSEADDR: + { + unsigned int *reuse = (unsigned int *) optval; + + if (*optlen < (socklen_t) sizeof *reuse) + { + set_errno (EINVAL); + return -1; + } + *reuse = reuseaddr (); + *optlen = (socklen_t) sizeof *reuse; + break; + } + + case SO_RCVBUF: + case SO_SNDBUF: + if (*optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + *(int *) optval = (optname == SO_RCVBUF) ? rmem () : wmem (); + break; + + case SO_RCVTIMEO: + case SO_SNDTIMEO: + { + struct timeval *time_out = (struct timeval *) optval; + + if (*optlen < (socklen_t) sizeof *time_out) + { + set_errno (EINVAL); + return -1; + } + DWORD ms = (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo (); + if (ms == 0 || ms == INFINITE) + { + time_out->tv_sec = 0; + time_out->tv_usec = 0; + } + else + { + time_out->tv_sec = ms / MSPERSEC; + time_out->tv_usec = ((ms % MSPERSEC) * USPERSEC) / MSPERSEC; + } + *optlen = (socklen_t) sizeof *time_out; + break; + } + + case SO_TYPE: + { + if (*optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + unsigned int *type = (unsigned int *) optval; + *type = get_socket_type (); + *optlen = (socklen_t) sizeof *type; + break; + } + + /* AF_UNIX sockets simply ignore all other SOL_SOCKET options. */ + + case SO_LINGER: + { + if (*optlen < (socklen_t) sizeof (struct linger)) + { + set_errno (EINVAL); + return -1; + } + struct linger *linger = (struct linger *) optval; + memset (linger, 0, sizeof *linger); + *optlen = (socklen_t) sizeof *linger; + break; + } + + default: + { + if (*optlen < (socklen_t) sizeof (int)) + { + set_errno (EINVAL); + return -1; + } + unsigned int *val = (unsigned int *) optval; + *val = 0; + *optlen = (socklen_t) sizeof *val; + break; + } + } + break; + + default: + set_errno (ENOPROTOOPT); + return -1; + } + + return 0; +} + +int +fhandler_socket_unix::ioctl (unsigned int cmd, void *p) +{ + int ret = -1; + + switch (cmd) + { + case FIOASYNC: + case _IOW('f', 125, int): + break; + case FIONREAD: + case _IOR('f', 127, int): + 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: + ret = fhandler_socket::ioctl (cmd, p); + break; + } + return ret; +} + +int +fhandler_socket_unix::fcntl (int cmd, intptr_t arg) +{ + if (get_flags () & O_PATH) + /* We're viewing the socket as a disk file, but + fhandler_base::fcntl suffices here. */ + return fhandler_base::fcntl (cmd, arg); + + int ret = -1; + + switch (cmd) + { + case F_SETOWN: + break; + case F_GETOWN: + break; + case F_SETFL: + { + const bool was_nonblocking = is_nonblocking (); + const int allowed_flags = O_APPEND | O_NONBLOCK; + int new_flags = arg & allowed_flags; + 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; + } + return ret; +} + +int +fhandler_socket_unix::fstat (struct stat *buf) +{ + if (!dev ().isfs ()) + /* fstat called on a socket. */ + return fhandler_socket::fstat (buf); + + /* stat/lstat on a socket file or fstat on a socket opened w/ O_PATH. */ + int ret = fhandler_base::fstat_fs (buf); + if (!ret) + { + buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFSOCK; + buf->st_size = 0; + } + return ret; +} + +int +fhandler_socket_unix::fstatvfs (struct statvfs *sfs) +{ + if (!dev ().isfs ()) + /* fstatvfs called on a socket. */ + return fhandler_socket::fstatvfs (sfs); + + /* statvfs on a socket file or fstatvfs on a socket opened w/ O_PATH. */ + if (get_flags () & O_PATH) + /* We already have a handle. */ + { + HANDLE h = get_handle (); + if (h) + return fstatvfs_by_handle (h, sfs); + } + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + return fh.fstatvfs (sfs); +} + +int +fhandler_socket_unix::fchmod (mode_t newmode) +{ + if (!dev ().isfs ()) + /* fchmod called on a socket. */ + return fhandler_socket::fchmod (newmode); + + /* chmod on a socket file. [We won't get here if fchmod is called + on a socket opened w/ O_PATH.] */ + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + /* Kludge: Don't allow to remove read bit on socket files for + user/group/other, if the accompanying write bit is set. It would + be nice to have exact permissions on a socket file, but it's + necessary that somebody able to access the socket can always read + the contents of the socket file to avoid spurious "permission + denied" messages. */ + newmode |= (newmode & (S_IWUSR | S_IWGRP | S_IWOTH)) << 1; + return fh.fchmod (S_IFSOCK | newmode); +} + +int +fhandler_socket_unix::fchown (uid_t uid, gid_t gid) +{ + if (!dev ().isfs ()) + /* fchown called on a socket. */ + return fhandler_socket::fchown (uid, gid); + + /* chown/lchown on a socket file. [We won't get here if fchown is + called on a socket opened w/ O_PATH.] */ + fhandler_disk_file fh (pc); + return fh.fchown (uid, gid); +} + +int +fhandler_socket_unix::facl (int cmd, int nentries, aclent_t *aclbufp) +{ + if (!dev ().isfs ()) + /* facl called on a socket. */ + return fhandler_socket::facl (cmd, nentries, aclbufp); + + /* facl on a socket file. [We won't get here if facl is called on a + socket opened w/ O_PATH.] */ + fhandler_disk_file fh (pc); + return fh.facl (cmd, nentries, aclbufp); +} + +int +fhandler_socket_unix::link (const char *newpath) +{ + if (!dev ().isfs ()) + /* linkat w/ AT_EMPTY_PATH called on a socket not opened w/ O_PATH. */ + return fhandler_socket::link (newpath); + /* link on a socket file or linkat w/ AT_EMPTY_PATH called on a + socket opened w/ O_PATH. */ + fhandler_disk_file fh (pc); + if (get_flags () & O_PATH) + fh.set_handle (get_handle ()); + return fh.link (newpath); +} + +#endif /* __WITH_AF_UNIX */ |