text: /*
   * Windows sockets
   *
   * Copyright 2021 Zebediah Figura for CodeWeavers
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
   */
  
  #if 0
  #pragma makedep unix
  #endif
  
  #include "config.h"
  #define _GNU_SOURCE /* for struct in6_pktinfo */
  #include <errno.h>
  #include <sys/types.h>
  #include <unistd.h>
  #ifdef HAVE_IFADDRS_H
  # include <ifaddrs.h>
  #endif
  #ifdef HAVE_NET_IF_H
  # include <net/if.h>
  #endif
  #ifdef HAVE_SYS_IOCTL_H
  # include <sys/ioctl.h>
  #endif
  #ifdef HAVE_SYS_SOCKET_H
  #include <sys/socket.h>
  #endif
  #ifdef HAVE_NETINET_IN_H
  # define __APPLE_USE_RFC_3542
  # include <netinet/in.h>
  #endif
  #ifdef HAVE_NETINET_TCP_H
  # include <netinet/tcp.h>
  #endif
  
  #ifdef HAVE_NETIPX_IPX_H
  # include <netipx/ipx.h>
  #elif defined(HAVE_LINUX_IPX_H)
  # ifdef HAVE_ASM_TYPES_H
  #  include <asm/types.h>
  # endif
  # ifdef HAVE_LINUX_TYPES_H
  #  include <linux/types.h>
  # endif
  # include <linux/ipx.h>
  #endif
  #if defined(SOL_IPX) || defined(SO_DEFAULT_HEADERS)
  # define HAS_IPX
  #endif
  
  #ifdef HAVE_LINUX_IRDA_H
  # ifdef HAVE_LINUX_TYPES_H
  #  include <linux/types.h>
  # endif
  # include <linux/irda.h>
  # define HAS_IRDA
  #endif
  
  #include "ntstatus.h"
  #define WIN32_NO_STATUS
  #include "windef.h"
  #include "winioctl.h"
  #define USE_WS_PREFIX
  #include "winsock2.h"
  #include "mswsock.h"
  #include "mstcpip.h"
  #include "ws2tcpip.h"
  #include "wsipx.h"
  #include "af_irda.h"
  #include "wine/afd.h"
  
  #include "unix_private.h"
  
  #if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE)
  /* TCP_KEEPALIVE is the Mac OS name for TCP_KEEPIDLE */
  #define TCP_KEEPIDLE TCP_KEEPALIVE
  #endif
  
  #if defined(linux) && !defined(IP_UNICAST_IF)
  #define IP_UNICAST_IF 50
  #endif
  
  WINE_DEFAULT_DEBUG_CHANNEL(winsock);
  
  union unix_sockaddr
  {
      struct sockaddr addr;
      struct sockaddr_in in;
      struct sockaddr_in6 in6;
  #ifdef HAS_IPX
      struct sockaddr_ipx ipx;
  #endif
  #ifdef HAS_IRDA
      struct sockaddr_irda irda;
  #endif
  };
  
  struct async_recv_ioctl
  {
      struct async_fileio io;
      WSABUF *control;
      struct WS_sockaddr *addr;
      int *addr_len;
      DWORD *ret_flags;
      int unix_flags;
      unsigned int count;
      struct iovec iov[1];
  };
  
  struct async_send_ioctl
  {
      struct async_fileio io;
      const struct WS_sockaddr *addr;
      int addr_len;
      int unix_flags;
      unsigned int sent_len;
      unsigned int count;
      unsigned int iov_cursor;
      struct iovec iov[1];
  };
  
  struct async_transmit_ioctl
  {
      struct async_fileio io;
      HANDLE file;
      char *buffer;
      unsigned int buffer_size;   /* allocated size of buffer */
      unsigned int read_len;      /* amount of valid data currently in the buffer */
      unsigned int head_cursor;   /* amount of header data already sent */
      unsigned int file_cursor;   /* amount of file data already sent */
      unsigned int buffer_cursor; /* amount of data currently in the buffer already sent */
      unsigned int tail_cursor;   /* amount of tail data already sent */
      unsigned int file_len;      /* total file length to send */
      DWORD flags;
      TRANSMIT_FILE_BUFFERS buffers;
      LARGE_INTEGER offset;
  };
  
  static NTSTATUS sock_errno_to_status( int err )
  {
      switch (err)
      {
          case EBADF:             return STATUS_INVALID_HANDLE;
          case EBUSY:             return STATUS_DEVICE_BUSY;
          case EPERM:
          case EACCES:            return STATUS_ACCESS_DENIED;
          case EFAULT:            return STATUS_ACCESS_VIOLATION;
          case EINVAL:            return STATUS_INVALID_PARAMETER;
          case ENFILE:
          case EMFILE:            return STATUS_TOO_MANY_OPENED_FILES;
          case EINPROGRESS:
          case EWOULDBLOCK:       return STATUS_DEVICE_NOT_READY;
          case EALREADY:          return STATUS_NETWORK_BUSY;
          case ENOTSOCK:          return STATUS_OBJECT_TYPE_MISMATCH;
          case EDESTADDRREQ:      return STATUS_INVALID_PARAMETER;
          case EMSGSIZE:          return STATUS_BUFFER_OVERFLOW;
          case EPROTONOSUPPORT:
          case ESOCKTNOSUPPORT:
          case EPFNOSUPPORT:
          case EAFNOSUPPORT:
          case EPROTOTYPE:        return STATUS_NOT_SUPPORTED;
          case ENOPROTOOPT:       return STATUS_INVALID_PARAMETER;
          case EOPNOTSUPP:        return STATUS_NOT_SUPPORTED;
          case EADDRINUSE:        return STATUS_SHARING_VIOLATION;
          case EADDRNOTAVAIL:     return STATUS_INVALID_PARAMETER;
          case ECONNREFUSED:      return STATUS_CONNECTION_REFUSED;
          case ESHUTDOWN:         return STATUS_PIPE_DISCONNECTED;
          case ENOTCONN:          return STATUS_INVALID_CONNECTION;
          case ETIMEDOUT:         return STATUS_IO_TIMEOUT;
          case ENETUNREACH:       return STATUS_NETWORK_UNREACHABLE;
          case EHOSTUNREACH:      return STATUS_HOST_UNREACHABLE;
          case ENETDOWN:          return STATUS_NETWORK_BUSY;
          case EPIPE:
          case ECONNRESET:        return STATUS_CONNECTION_RESET;
          case ECONNABORTED:      return STATUS_CONNECTION_ABORTED;
          case EISCONN:           return STATUS_CONNECTION_ACTIVE;
  
          case 0:                 return STATUS_SUCCESS;
          default:
              FIXME( "unknown errno %d\n", err );
              return STATUS_UNSUCCESSFUL;
      }
  }
  
  static socklen_t sockaddr_to_unix( const struct WS_sockaddr *wsaddr, int wsaddrlen, union unix_sockaddr *uaddr )
  {
      memset( uaddr, 0, sizeof(*uaddr) );
  
      switch (wsaddr->sa_family)
      {
      case WS_AF_INET:
      {
          struct WS_sockaddr_in win = {0};
  
          if (wsaddrlen < sizeof(win)) return 0;
          memcpy( &win, wsaddr, sizeof(win) );
          uaddr->in.sin_family = AF_INET;
          uaddr->in.sin_port = win.sin_port;
          memcpy( &uaddr->in.sin_addr, &win.sin_addr, sizeof(win.sin_addr) );
          return sizeof(uaddr->in);
      }
  
      case WS_AF_INET6:
      {
          struct WS_sockaddr_in6 win = {0};
  
          if (wsaddrlen < sizeof(win)) return 0;
          memcpy( &win, wsaddr, sizeof(win) );
          uaddr->in6.sin6_family = AF_INET6;
          uaddr->in6.sin6_port = win.sin6_port;
          uaddr->in6.sin6_flowinfo = win.sin6_flowinfo;
          memcpy( &uaddr->in6.sin6_addr, &win.sin6_addr, sizeof(win.sin6_addr) );
  #ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
          uaddr->in6.sin6_scope_id = win.sin6_scope_id;
  #endif
          return sizeof(uaddr->in6);
      }
  
  #ifdef HAS_IPX
      case WS_AF_IPX:
      {
          struct WS_sockaddr_ipx win = {0};
  
          if (wsaddrlen < sizeof(win)) return 0;
          memcpy( &win, wsaddr, sizeof(win) );
          uaddr->ipx.sipx_family = AF_IPX;
          memcpy( &uaddr->ipx.sipx_network, win.sa_netnum, sizeof(win.sa_netnum) );
          memcpy( &uaddr->ipx.sipx_node, win.sa_nodenum, sizeof(win.sa_nodenum) );
          uaddr->ipx.sipx_port = win.sa_socket;
          return sizeof(uaddr->ipx);
      }
  #endif
  
  #ifdef HAS_IRDA
      case WS_AF_IRDA:
      {
          SOCKADDR_IRDA win = {0};
          unsigned int lsap_sel;
  
          if (wsaddrlen < sizeof(win)) return 0;
          memcpy( &win, wsaddr, sizeof(win) );
          uaddr->irda.sir_family = AF_IRDA;
          if (sscanf( win.irdaServiceName, "LSAP-SEL%u", &lsap_sel ) == 1)
              uaddr->irda.sir_lsap_sel = lsap_sel;
          else
          {
              uaddr->irda.sir_lsap_sel = LSAP_ANY;
              memcpy( uaddr->irda.sir_name, win.irdaServiceName, sizeof(win.irdaServiceName) );
          }
          memcpy( &uaddr->irda.sir_addr, win.irdaDeviceID, sizeof(win.irdaDeviceID) );
          return sizeof(uaddr->irda);
      }
  #endif
  
      case WS_AF_UNSPEC:
          switch (wsaddrlen)
          {
          default: /* likely an ipv4 address */
          case sizeof(struct WS_sockaddr_in):
              return sizeof(uaddr->in);
  
  #ifdef HAS_IPX
          case sizeof(struct WS_sockaddr_ipx):
              return sizeof(uaddr->ipx);
  #endif
  
  #ifdef HAS_IRDA
          case sizeof(SOCKADDR_IRDA):
              return sizeof(uaddr->irda);
  #endif
  
          case sizeof(struct WS_sockaddr_in6):
              return sizeof(uaddr->in6);
          }
  
      default:
          FIXME( "unknown address family %u\n", wsaddr->sa_family );
          return 0;
      }
  }
  
  static int sockaddr_from_unix( const union unix_sockaddr *uaddr, struct WS_sockaddr *wsaddr, socklen_t wsaddrlen )
  {
      memset( wsaddr, 0, wsaddrlen );
  
      switch (uaddr->addr.sa_family)
      {
      case AF_INET:
      {
          struct WS_sockaddr_in win = {0};
  
          if (wsaddrlen < sizeof(win)) return -1;
          win.sin_family = WS_AF_INET;
          win.sin_port = uaddr->in.sin_port;
          memcpy( &win.sin_addr, &uaddr->in.sin_addr, sizeof(win.sin_addr) );
          memcpy( wsaddr, &win, sizeof(win) );
          return sizeof(win);
      }
  
      case AF_INET6:
      {
          struct WS_sockaddr_in6 win = {0};
  
          if (wsaddrlen < sizeof(win)) return -1;
          win.sin6_family = WS_AF_INET6;
          win.sin6_port = uaddr->in6.sin6_port;
          win.sin6_flowinfo = uaddr->in6.sin6_flowinfo;
          memcpy( &win.sin6_addr, &uaddr->in6.sin6_addr, sizeof(win.sin6_addr) );
  #ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
          win.sin6_scope_id = uaddr->in6.sin6_scope_id;
  #endif
          memcpy( wsaddr, &win, sizeof(win) );
          return sizeof(win);
      }
  
  #ifdef HAS_IPX
      case AF_IPX:
      {
          struct WS_sockaddr_ipx win = {0};
  
          if (wsaddrlen < sizeof(win)) return -1;
          win.sa_family = WS_AF_IPX;
          memcpy( win.sa_netnum, &uaddr->ipx.sipx_network, sizeof(win.sa_netnum) );
          memcpy( win.sa_nodenum, &uaddr->ipx.sipx_node, sizeof(win.sa_nodenum) );
          win.sa_socket = uaddr->ipx.sipx_port;
          memcpy( wsaddr, &win, sizeof(win) );
          return sizeof(win);
      }
  #endif
  
  #ifdef HAS_IRDA
      case AF_IRDA:
      {
          SOCKADDR_IRDA win;
  
          if (wsaddrlen < sizeof(win)) return -1;
          win.irdaAddressFamily = WS_AF_IRDA;
          memcpy( win.irdaDeviceID, &uaddr->irda.sir_addr, sizeof(win.irdaDeviceID) );
          if (uaddr->irda.sir_lsap_sel != LSAP_ANY)
              snprintf( win.irdaServiceName, sizeof(win.irdaServiceName), "LSAP-SEL%u", uaddr->irda.sir_lsap_sel );
          else
              memcpy( win.irdaServiceName, uaddr->irda.sir_name, sizeof(win.irdaServiceName) );
          memcpy( wsaddr, &win, sizeof(win) );
          return sizeof(win);
      }
  #endif
  
      case AF_UNSPEC:
          return 0;
  
      default:
          FIXME( "unknown address family %d\n", uaddr->addr.sa_family );
          return -1;
      }
  }
  
  #ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
  #if defined(IP_PKTINFO) || defined(IP_RECVDSTADDR)
  static WSACMSGHDR *fill_control_message( int level, int type, WSACMSGHDR *current, ULONG *maxsize, void *data, int len )
  {
      ULONG msgsize = sizeof(WSACMSGHDR) + WSA_CMSG_ALIGN(len);
      char *ptr = (char *) current + sizeof(WSACMSGHDR);
  
      if (msgsize > *maxsize)
          return NULL;
      *maxsize -= msgsize;
      current->cmsg_len = sizeof(WSACMSGHDR) + len;
      current->cmsg_level = level;
      current->cmsg_type = type;
      memcpy(ptr, data, len);
      return (WSACMSGHDR *)(ptr + WSA_CMSG_ALIGN(len));
  }
  #endif /* defined(IP_PKTINFO) || defined(IP_RECVDSTADDR) */
  
  static int convert_control_headers(struct msghdr *hdr, WSABUF *control)
  {
  #if defined(IP_PKTINFO) || defined(IP_RECVDSTADDR)
      WSACMSGHDR *cmsg_win = (WSACMSGHDR *)control->buf, *ptr;
      ULONG ctlsize = control->len;
      struct cmsghdr *cmsg_unix;
  
      ptr = cmsg_win;
      for (cmsg_unix = CMSG_FIRSTHDR(hdr); cmsg_unix != NULL; cmsg_unix = CMSG_NXTHDR(hdr, cmsg_unix))
      {
          switch (cmsg_unix->cmsg_level)
          {
              case IPPROTO_IP:
                  switch (cmsg_unix->cmsg_type)
                  {
  #if defined(IP_PKTINFO)
                      case IP_PKTINFO:
                      {
                          const struct in_pktinfo *data_unix = (struct in_pktinfo *)CMSG_DATA(cmsg_unix);
                          struct WS_in_pktinfo data_win;
  
                          memcpy( &data_win.ipi_addr, &data_unix->ipi_addr.s_addr, 4 ); /* 4 bytes = 32 address bits */
                          data_win.ipi_ifindex = data_unix->ipi_ifindex;
                          ptr = fill_control_message( WS_IPPROTO_IP, WS_IP_PKTINFO, ptr, &ctlsize,
                                                      (void *)&data_win, sizeof(data_win) );
                          if (!ptr) goto error;
                          break;
                      }
  #elif defined(IP_RECVDSTADDR)
                      case IP_RECVDSTADDR:
                      {
                          const struct in_addr *addr_unix = (struct in_addr *)CMSG_DATA(cmsg_unix);
                          struct WS_in_pktinfo data_win;
  
                          memcpy( &data_win.ipi_addr, &addr_unix->s_addr, 4 ); /* 4 bytes = 32 address bits */
                          data_win.ipi_ifindex = 0; /* FIXME */
                          ptr = fill_control_message( WS_IPPROTO_IP, WS_IP_PKTINFO, ptr, &ctlsize,
                                                      (void *)&data_win, sizeof(data_win) );
                          if (!ptr) goto error;
                          break;
                      }
  #endif /* IP_PKTINFO */
                      default:
                          FIXME("Unhandled IPPROTO_IP message header type %d\n", cmsg_unix->cmsg_type);
                          break;
                  }
                  break;
  
              case IPPROTO_IPV6:
                  switch (cmsg_unix->cmsg_type)
                  {
  #if defined(IPV6_HOPLIMIT)
                      case IPV6_HOPLIMIT:
                      {
                          ptr = fill_control_message( WS_IPPROTO_IPV6, WS_IPV6_HOPLIMIT, ptr, &ctlsize,
                                                      CMSG_DATA(cmsg_unix), sizeof(INT) );
                          if (!ptr) goto error;
                          break;
                      }
  #endif /* IPV6_HOPLIMIT */
  
  #if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO_IPI6_ADDR)
                      case IPV6_PKTINFO:
                      {
                          struct in6_pktinfo *data_unix = (struct in6_pktinfo *)CMSG_DATA(cmsg_unix);
                          struct WS_in6_pktinfo data_win;
  
                          memcpy(&data_win.ipi6_addr, &data_unix->ipi6_addr.s6_addr, 16);
                          data_win.ipi6_ifindex = data_unix->ipi6_ifindex;
                          ptr = fill_control_message( WS_IPPROTO_IPV6, WS_IPV6_PKTINFO, ptr, &ctlsize,
                                                      (void *)&data_win, sizeof(data_win) );
                          if (!ptr) goto error;
                          break;
                      }
  #endif /* IPV6_PKTINFO */
  
                      default:
                          FIXME("Unhandled IPPROTO_IPV6 message header type %d\n", cmsg_unix->cmsg_type);
                          break;
                  }
                  break;
  
              default:
                  FIXME("Unhandled message header level %d\n", cmsg_unix->cmsg_level);
                  break;
          }
      }
  
      control->len = (char *)ptr - (char *)cmsg_win;
      return 1;
  
  error:
      control->len = 0;
      return 0;
  #else /* defined(IP_PKTINFO) || defined(IP_RECVDSTADDR) */
      control->len = 0;
      return 1;
  #endif /* defined(IP_PKTINFO) || defined(IP_RECVDSTADDR) */
  }
  #endif /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */
  
  static NTSTATUS try_recv( int fd, struct async_recv_ioctl *async, ULONG_PTR *size )
  {
  #ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
      char control_buffer[512];
  #endif
      union unix_sockaddr unix_addr;
      struct msghdr hdr;
      NTSTATUS status;
      ssize_t ret;
  
      memset( &hdr, 0, sizeof(hdr) );
      if (async->addr)
      {
          hdr.msg_name = &unix_addr.addr;
          hdr.msg_namelen = sizeof(unix_addr);
      }
      hdr.msg_iov = async->iov;
      hdr.msg_iovlen = async->count;
  #ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
      hdr.msg_control = control_buffer;
      hdr.msg_controllen = sizeof(control_buffer);
  #endif
      while ((ret = virtual_locked_recvmsg( fd, &hdr, async->unix_flags )) < 0 && errno == EINTR);
  
      if (ret < 0)
      {
          /* Unix-like systems return EINVAL when attempting to read OOB data from
           * an empty socket buffer; Windows returns WSAEWOULDBLOCK. */
          if ((async->unix_flags & MSG_OOB) && errno == EINVAL)
              errno = EWOULDBLOCK;
  
          if (errno != EWOULDBLOCK) WARN( "recvmsg: %s\n", strerror( errno ) );
          return sock_errno_to_status( errno );
      }
  
      status = (hdr.msg_flags & MSG_TRUNC) ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
  
  #ifdef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
      if (async->control)
      {
          ERR( "Message control headers cannot be properly supported on this system.\n" );
          async->control->len = 0;
      }
  #else
      if (async->control && !convert_control_headers( &hdr, async->control ))
      {
          WARN( "Application passed insufficient room for control headers.\n" );
          *async->ret_flags |= WS_MSG_CTRUNC;
          status = STATUS_BUFFER_OVERFLOW;
      }
  #endif
  
      /* If this socket is connected, Linux doesn't give us msg_name and
       * msg_namelen from recvmsg, but it does set msg_namelen to zero.
       *
       * MSDN says that the address is ignored for connection-oriented sockets, so
       * don't try to translate it.
       */
      if (async->addr && hdr.msg_namelen)
          *async->addr_len = sockaddr_from_unix( &unix_addr, async->addr, *async->addr_len );
  
      *size = ret;
      return status;
  }
  
  static NTSTATUS async_recv_proc( void *user, ULONG_PTR *info, NTSTATUS status )
  {
      struct async_recv_ioctl *async = user;
      ULONG_PTR information = 0;
      int fd, needs_close;
  
      TRACE( "%#x\n", status );
  
      if (status == STATUS_ALERTED)
      {
          if ((status = server_get_unix_fd( async->io.handle, 0, &fd, &needs_close, NULL, NULL )))
              return status;
  
          status = try_recv( fd, async, &information );
          TRACE( "got status %#x, %#lx bytes read\n", status, information );
  
          if (status == STATUS_DEVICE_NOT_READY)
              status = STATUS_PENDING;
  
          if (needs_close) close( fd );
      }
      if (status != STATUS_PENDING)
      {
          *info = information;
          release_fileio( &async->io );
      }
      return status;
  }
  
  static NTSTATUS sock_recv( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user, IO_STATUS_BLOCK *io,
                             int fd, const WSABUF *buffers, unsigned int count, WSABUF *control,
                             struct WS_sockaddr *addr, int *addr_len, DWORD *ret_flags, int unix_flags, int force_async )
  {
      struct async_recv_ioctl *async;
      ULONG_PTR information;
      HANDLE wait_handle;
      DWORD async_size;
      NTSTATUS status;
      unsigned int i;
      ULONG options;
  
      if (unix_flags & MSG_OOB)
      {
          int oobinline;
          socklen_t len = sizeof(oobinline);
          if (!getsockopt( fd, SOL_SOCKET, SO_OOBINLINE, (char *)&oobinline, &len ) && oobinline)
              return STATUS_INVALID_PARAMETER;
      }
  
      for (i = 0; i < count; ++i)
      {
          if (!virtual_check_buffer_for_write( buffers[i].buf, buffers[i].len ))
              return STATUS_ACCESS_VIOLATION;
      }
  
      async_size = offsetof( struct async_recv_ioctl, iov[count] );
  
      if (!(async = (struct async_recv_ioctl *)alloc_fileio( async_size, async_recv_proc, handle )))
          return STATUS_NO_MEMORY;
  
      async->count = count;
      for (i = 0; i < count; ++i)
      {
          async->iov[i].iov_base = buffers[i].buf;
          async->iov[i].iov_len = buffers[i].len;
      }
      async->unix_flags = unix_flags;
      async->control = control;
      async->addr = addr;
      async->addr_len = addr_len;
      async->ret_flags = ret_flags;
  
      status = try_recv( fd, async, &information );
  
      if (status != STATUS_SUCCESS && status != STATUS_BUFFER_OVERFLOW && status != STATUS_DEVICE_NOT_READY)
      {
          release_fileio( &async->io );
          return status;
      }
  
      if (status == STATUS_DEVICE_NOT_READY && force_async)
          status = STATUS_PENDING;
  
      SERVER_START_REQ( recv_socket )
      {
          req->status = status;
          req->total  = information;
          req->async  = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) );
          req->oob    = !!(unix_flags & MSG_OOB);
          status = wine_server_call( req );
          wait_handle = wine_server_ptr_handle( reply->wait );
          options     = reply->options;
          if ((!NT_ERROR(status) || wait_handle) && status != STATUS_PENDING)
          {
              io->Status = status;
              io->Information = information;
          }
      }
      SERVER_END_REQ;
  
      if (status != STATUS_PENDING) release_fileio( &async->io );
  
      if (wait_handle) status = wait_async( wait_handle, options & FILE_SYNCHRONOUS_IO_ALERT );
      return status;
  }
  
  
  struct async_poll_ioctl
  {
      struct async_fileio io;
      unsigned int count;
      struct afd_poll_params *input, *output;
      struct poll_socket_output sockets[1];
  };
  
  static ULONG_PTR fill_poll_output( struct async_poll_ioctl *async, NTSTATUS status )
  {
      struct afd_poll_params *input = async->input, *output = async->output;
      unsigned int i, count = 0;
  
      memcpy( output, input, offsetof( struct afd_poll_params, sockets[0] ) );
  
      if (!status)
      {
          for (i = 0; i < async->count; ++i)
          {
              if (async->sockets[i].flags)
              {
                  output->sockets[count].socket = input->sockets[i].socket;
                  output->sockets[count].flags = async->sockets[i].flags;
                  output->sockets[count].status = async->sockets[i].status;
                  ++count;
              }
          }
      }
      output->count = count;
      return offsetof( struct afd_poll_params, sockets[count] );
  }
  
  static NTSTATUS async_poll_proc( void *user, ULONG_PTR *info, NTSTATUS status )
  {
      struct async_poll_ioctl *async = user;
      ULONG_PTR information = 0;
  
      if (status == STATUS_ALERTED)
      {
          SERVER_START_REQ( get_async_result )
          {
              req->user_arg = wine_server_client_ptr( async );
              wine_server_set_reply( req, async->sockets, async->count * sizeof(async->sockets[0]) );
              status = wine_server_call( req );
          }
          SERVER_END_REQ;
  
          information = fill_poll_output( async, status );
      }
  
      if (status != STATUS_PENDING)
      {
          *info = information;
          free( async->input );
          release_fileio( &async->io );
      }
      return status;
  }
  
  
  /* we could handle this ioctl entirely on the server side, but the differing
   * structure size makes it painful */
  static NTSTATUS sock_poll( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user, IO_STATUS_BLOCK *io,
                             void *in_buffer, ULONG in_size, void *out_buffer, ULONG out_size )
  {
      const struct afd_poll_params *params = in_buffer;
      struct poll_socket_input *input;
      struct async_poll_ioctl *async;
      HANDLE wait_handle;
      DWORD async_size;
      NTSTATUS status;
      unsigned int i;
      ULONG options;
  
      if (in_size < sizeof(*params) || out_size < in_size || !params->count
              || in_size < offsetof( struct afd_poll_params, sockets[params->count] ))
          return STATUS_INVALID_PARAMETER;
  
      TRACE( "timeout %s, count %u, unknown %#x, padding (%#x, %#x, %#x), sockets[0] {%04lx, %#x}\n",
              wine_dbgstr_longlong(params->timeout), params->count, params->unknown,
              params->padding[0], params->padding[1], params->padding[2],
              params->sockets[0].socket, params->sockets[0].flags );
  
      if (params->unknown) FIXME( "unknown boolean is %#x\n", params->unknown );
      if (params->padding[0]) FIXME( "padding[0] is %#x\n", params->padding[0] );
      if (params->padding[1]) FIXME( "padding[1] is %#x\n", params->padding[1] );
      if (params->padding[2]) FIXME( "padding[2] is %#x\n", params->padding[2] );
      for (i = 0; i < params->count; ++i)
      {
          if (params->sockets[i].flags & ~0x1ff)
              FIXME( "unknown socket flags %#x\n", params->sockets[i].flags );
      }
  
      if (!(input = malloc( params->count * sizeof(*input) )))
          return STATUS_NO_MEMORY;
  
      async_size = offsetof( struct async_poll_ioctl, sockets[params->count] );
  
      if (!(async = (struct async_poll_ioctl *)alloc_fileio( async_size, async_poll_proc, handle )))
      {
          free( input );
          return STATUS_NO_MEMORY;
      }
  
      if (!(async->input = malloc( in_size )))
      {
          release_fileio( &async->io );
          free( input );
          return STATUS_NO_MEMORY;
      }
      memcpy( async->input, in_buffer, in_size );
  
      async->count = params->count;
      async->output = out_buffer;
  
      for (i = 0; i < params->count; ++i)
      {
          input[i].socket = params->sockets[i].socket;
          input[i].flags = params->sockets[i].flags;
      }
  
      SERVER_START_REQ( poll_socket )
      {
          req->async = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) );
          req->timeout = params->timeout;
          wine_server_add_data( req, input, params->count * sizeof(*input) );
          wine_server_set_reply( req, async->sockets, params->count * sizeof(async->sockets[0]) );
          status = wine_server_call( req );
          wait_handle = wine_server_ptr_handle( reply->wait );
          options = reply->options;
          if (wait_handle && status != STATUS_PENDING)
          {
              io->Status = status;
              io->Information = fill_poll_output( async, status );
          }
      }
      SERVER_END_REQ;
  
      free( input );
  
      if (status != STATUS_PENDING)
      {
          free( async->input );
          release_fileio( &async->io );
      }
  
      if (wait_handle) status = wait_async( wait_handle, (options & FILE_SYNCHRONOUS_IO_ALERT) );
      return status;
  }
  
  static NTSTATUS try_send( int fd, struct async_send_ioctl *async )
  {
      union unix_sockaddr unix_addr;
      struct msghdr hdr;
      ssize_t ret;
  
      memset( &hdr, 0, sizeof(hdr) );
      if (async->addr)
      {
          hdr.msg_name = &unix_addr;
          hdr.msg_namelen = sockaddr_to_unix( async->addr, async->addr_len, &unix_addr );
          if (!hdr.msg_namelen)
          {
              ERR( "failed to convert address\n" );
              return STATUS_ACCESS_VIOLATION;
          }
  
  #if defined(HAS_IPX) && defined(SOL_IPX)
          if (async->addr->sa_family == WS_AF_IPX)
          {
              int type;
              socklen_t len = sizeof(type);
  
              /* The packet type is stored at the IPX socket level. At least the
               * linux kernel seems to do something with it in case hdr.msg_name
               * is NULL. Nonetheless we can use it to store the packet type, and
               * then we can retrieve it using getsockopt. After that we can set
               * the IPX type in the sockaddr_ipx structure with the stored value.
               */
              if (getsockopt(fd, SOL_IPX, IPX_TYPE, &type, &len) >= 0)
                  unix_addr.ipx.sipx_type = type;
          }
  #endif
      }
  
      hdr.msg_iov = async->iov + async->iov_cursor;
      hdr.msg_iovlen = async->count - async->iov_cursor;
  
      while ((ret = sendmsg( fd, &hdr, async->unix_flags )) == -1)
      {
          if (errno == EISCONN)
          {
              hdr.msg_name = NULL;
              hdr.msg_namelen = 0;
          }
          else if (errno != EINTR)
          {
              if (errno != EWOULDBLOCK) WARN( "sendmsg: %s\n", strerror( errno ) );
              return sock_errno_to_status( errno );
          }
      }
  
      async->sent_len += ret;
  
      while (async->iov_cursor < async->count && ret >= async->iov[async->iov_cursor].iov_len)
          ret -= async->iov[async->iov_cursor++].iov_len;
      if (async->iov_cursor < async->count)
      {
          async->iov[async->iov_cursor].iov_base = (char *)async->iov[async->iov_cursor].iov_base + ret;
          async->iov[async->iov_cursor].iov_len -= ret;
          return STATUS_DEVICE_NOT_READY;
      }
      return STATUS_SUCCESS;
  }
  
  static NTSTATUS async_send_proc( void *user, ULONG_PTR *info, NTSTATUS status )
  {
      struct async_send_ioctl *async = user;
      int fd, needs_close;
  
      TRACE( "%#x\n", status );
  
      if (status == STATUS_ALERTED)
      {
          if ((status = server_get_unix_fd( async->io.handle, 0, &fd, &needs_close, NULL, NULL )))
              return status;
  
          status = try_send( fd, async );
          TRACE( "got status %#x\n", status );
  
          if (status == STATUS_DEVICE_NOT_READY)
              status = STATUS_PENDING;
  
          if (needs_close) close( fd );
      }
      if (status != STATUS_PENDING)
      {
          *info = async->sent_len;
          release_fileio( &async->io );
      }
      return status;
  }
  
  static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user,
                             IO_STATUS_BLOCK *io, int fd, const WSABUF *buffers, unsigned int count,
                             const struct WS_sockaddr *addr, unsigned int addr_len, int unix_flags, int force_async )
  {
      struct async_send_ioctl *async;
      HANDLE wait_handle;
      DWORD async_size;
      NTSTATUS status;
      unsigned int i;
      ULONG options;
  
      async_size = offsetof( struct async_send_ioctl, iov[count] );
  
      if (!(async = (struct async_send_ioctl *)alloc_fileio( async_size, async_send_proc, handle )))
          return STATUS_NO_MEMORY;
  
      async->count = count;
      for (i = 0; i < count; ++i)
      {
          async->iov[i].iov_base = buffers[i].buf;
          async->iov[i].iov_len = buffers[i].len;
      }
      async->unix_flags = unix_flags;
      async->addr = addr;
      async->addr_len = addr_len;
      async->iov_cursor = 0;
      async->sent_len = 0;
  
      status = try_send( fd, async );
  
      if (status != STATUS_SUCCESS && status != STATUS_DEVICE_NOT_READY)
      {
          release_fileio( &async->io );
          return status;
      }
  
      if (status == STATUS_DEVICE_NOT_READY && force_async)
          status = STATUS_PENDING;
  
      SERVER_START_REQ( send_socket )
      {
          req->status = status;
          req->total  = async->sent_len;
          req->async  = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) );
          status = wine_server_call( req );
          wait_handle = wine_server_ptr_handle( reply->wait );
          options     = reply->options;
          if ((!NT_ERROR(status) || wait_handle) && status != STATUS_PENDING)
          {
              io->Status = status;
              io->Information = async->sent_len;
          }
      }
      SERVER_END_REQ;
  
      if (status != STATUS_PENDING) release_fileio( &async->io );
  
      if (wait_handle) status = wait_async( wait_handle, options & FILE_SYNCHRONOUS_IO_ALERT );
      return status;
  }
  
  static ssize_t do_send( int fd, const void *buffer, size_t len, int flags )
  {
      ssize_t ret;
      while ((ret = send( fd, buffer, len, flags )) < 0 && errno == EINTR);
      if (ret < 0 && errno != EWOULDBLOCK) WARN( "send: %s\n", strerror( errno ) );
      return ret;
  }
  
  static NTSTATUS try_transmit( int sock_fd, int file_fd, struct async_transmit_ioctl *async )
  {
      ssize_t ret;
  
      while (async->head_cursor < async->buffers.HeadLength)
      {
          TRACE( "sending %u bytes of header data\n", async->buffers.HeadLength - async->head_cursor );
          ret = do_send( sock_fd, (char *)async->buffers.Head + async->head_cursor,
                         async->buffers.HeadLength - async->head_cursor, 0 );
          if (ret < 0) return sock_errno_to_status( errno );
          TRACE( "send returned %zd\n", ret );
          async->head_cursor += ret;
      }
  
      while (async->buffer_cursor < async->read_len)
      {
          TRACE( "sending %u bytes of file data\n", async->read_len - async->buffer_cursor );
          ret = do_send( sock_fd, async->buffer + async->buffer_cursor,
                         async->read_len - async->buffer_cursor, 0 );
          if (ret < 0) return sock_errno_to_status( errno );
          TRACE( "send returned %zd\n", ret );
          async->buffer_cursor += ret;
          async->file_cursor += ret;
      }
  
      if (async->file && async->buffer_cursor == async->read_len)
      {
          unsigned int read_size = async->buffer_size;
  
          if (async->file_len)
              read_size = min( read_size, async->file_len - async->file_cursor );
  
          TRACE( "reading %u bytes of file data\n", read_size );
          do
          {
              if (async->offset.QuadPart == FILE_USE_FILE_POINTER_POSITION)
                  ret = read( file_fd, async->buffer, read_size );
              else
                  ret = pread( file_fd, async->buffer, read_size, async->offset.QuadPart );
          } while (ret < 0 && errno == EINTR);
          if (ret < 0) return errno_to_status( errno );
          TRACE( "read returned %zd\n", ret );
  
          async->read_len = ret;
          async->buffer_cursor = 0;
          if (async->offset.QuadPart != FILE_USE_FILE_POINTER_POSITION)
              async->offset.QuadPart += ret;
  
          if (ret < read_size || (async->file_len && async->file_cursor == async->file_len))
              async->file = NULL;
          return STATUS_PENDING; /* still more data to send */
      }
  
      while (async->tail_cursor < async->buffers.TailLength)
      {
          TRACE( "sending %u bytes of tail data\n", async->buffers.TailLength - async->tail_cursor );
          ret = do_send( sock_fd, (char *)async->buffers.Tail + async->tail_cursor,
                         async->buffers.TailLength - async->tail_cursor, 0 );
          if (ret < 0) return sock_errno_to_status( errno );
          TRACE( "send returned %zd\n", ret );
          async->tail_cursor += ret;
      }
  
      return STATUS_SUCCESS;
  }
  
  static NTSTATUS async_transmit_proc( void *user, ULONG_PTR *info, NTSTATUS status )
  {
      int sock_fd, file_fd = -1, sock_needs_close = FALSE, file_needs_close = FALSE;
      struct async_transmit_ioctl *async = user;
  
      TRACE( "%#x\n", status );
  
      if (status == STATUS_ALERTED)
      {
          if ((status = server_get_unix_fd( async->io.handle, 0, &sock_fd, &sock_needs_close, NULL, NULL )))
              return status;
  
          if (async->file && (status = server_get_unix_fd( async->file, 0, &file_fd, &file_needs_close, NULL, NULL )))
          {
              if (sock_needs_close) close( sock_fd );
              return status;
          }
  
          status = try_transmit( sock_fd, file_fd, async );
          TRACE( "got status %#x\n", status );
  
          if (status == STATUS_DEVICE_NOT_READY)
              status = STATUS_PENDING;
  
          if (sock_needs_close) close( sock_fd );
          if (file_needs_close) close( file_fd );
      }
      if (status != STATUS_PENDING)
      {
          *info = async->head_cursor + async->file_cursor + async->tail_cursor;
          release_fileio( &async->io );
      }
      return status;
  }
  
  static NTSTATUS sock_transmit( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user,
                                 IO_STATUS_BLOCK *io, int fd, const struct afd_transmit_params *params )
  {
      int file_fd, file_needs_close = FALSE;
      struct async_transmit_ioctl *async;
      enum server_fd_type file_type;
      union unix_sockaddr addr;
      socklen_t addr_len;
      HANDLE wait_handle;
      NTSTATUS status;
      ULONG options;
  
      addr_len = sizeof(addr);
      if (getpeername( fd, &addr.addr, &addr_len ) != 0)
          return STATUS_INVALID_CONNECTION;
  
      if (params->file)
      {
          if ((status = server_get_unix_fd( params->file, 0, &file_fd, &file_needs_close, &file_type, NULL )))
              return status;
          if (file_needs_close) close( file_fd );
  
          if (file_type != FD_TYPE_FILE)
          {
              FIXME( "unsupported file type %#x\n", file_type );
              return STATUS_NOT_IMPLEMENTED;
          }
      }
  
      if (!(async = (struct async_transmit_ioctl *)alloc_fileio( sizeof(*async), async_transmit_proc, handle )))
          return STATUS_NO_MEMORY;
  
      async->file = params->file;
      async->buffer_size = params->buffer_size ? params->buffer_size : 65536;
      if (!(async->buffer = malloc( async->buffer_size )))
      {
          release_fileio( &async->io );
          return STATUS_NO_MEMORY;
      }
      async->read_len = 0;
      async->head_cursor = 0;
      async->file_cursor = 0;
      async->buffer_cursor = 0;
      async->tail_cursor = 0;
      async->file_len = params->file_len;
      async->flags = params->flags;
      async->buffers = params->buffers;
      async->offset = params->offset;
  
      SERVER_START_REQ( send_socket )
      {
          req->status = STATUS_PENDING;
          req->total  = 0;
          req->async  = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) );
          status = wine_server_call( req );
          wait_handle = wine_server_ptr_handle( reply->wait );
          options     = reply->options;
          /* In theory we'd fill the iosb here, as above in sock_send(), but it's
           * actually currently impossible to get STATUS_SUCCESS. The server will
           * either return STATUS_PENDING or an error code, and in neither case
           * should the iosb be filled. */
      }
      SERVER_END_REQ;
  
      if (status != STATUS_PENDING) release_fileio( &async->io );
  
      if (wait_handle) status = wait_async( wait_handle, options & FILE_SYNCHRONOUS_IO_ALERT );
      return status;
  }
  
  static void complete_async( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user,
                              IO_STATUS_BLOCK *io, NTSTATUS status, ULONG_PTR information )
  {
      ULONG_PTR iosb_ptr = iosb_client_ptr(io);
  
      io->Status = status;
      io->Information = information;
      if (event) NtSetEvent( event, NULL );
      if (apc) NtQueueApcThread( GetCurrentThread(), (PNTAPCFUNC)apc, (ULONG_PTR)apc_user, iosb_ptr, 0 );
      if (apc_user) add_completion( handle, (ULONG_PTR)apc_user, status, information, FALSE );
  }
  
  
  static NTSTATUS do_getsockopt( HANDLE handle, IO_STATUS_BLOCK *io, int level,
                                 int option, void *out_buffer, ULONG out_size )
  {
      int fd, needs_close = FALSE;
      socklen_t len = out_size;
      NTSTATUS status;
      int ret;
  
      if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
          return status;
  
      ret = getsockopt( fd, level, option, out_buffer, &len );
      if (needs_close) close( fd );
      if (ret) return sock_errno_to_status( errno );
      io->Information = len;
      return STATUS_SUCCESS;
  }
  
  
  static NTSTATUS do_setsockopt( HANDLE handle, IO_STATUS_BLOCK *io, int level,
                                 int option, const void *optval, socklen_t optlen )
  {
      int fd, needs_close = FALSE;
      NTSTATUS status;
      int ret;
  
      if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
          return status;
  
      ret = setsockopt( fd, level, option, optval, optlen );
      if (needs_close) close( fd );
      return ret ? sock_errno_to_status( errno ) : STATUS_SUCCESS;
  }
  
  
  NTSTATUS sock_ioctl( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user, IO_STATUS_BLOCK *io,
                       ULONG code, void *in_buffer, ULONG in_size, void *out_buffer, ULONG out_size )
  {
      int fd, needs_close = FALSE;
      NTSTATUS status;
  
      TRACE( "handle %p, code %#x, in_buffer %p, in_size %u, out_buffer %p, out_size %u\n",
             handle, code, in_buffer, in_size, out_buffer, out_size );
  
      switch (code)
      {
          case IOCTL_AFD_BIND:
          {
              const struct afd_bind_params *params = in_buffer;
  
              if (params->unknown) FIXME( "bind: got unknown %#x\n", params->unknown );
  
              status = STATUS_BAD_DEVICE_TYPE;
              break;
          }
  
          case IOCTL_AFD_GETSOCKNAME:
              if (in_size) FIXME( "unexpected input size %u\n", in_size );
  
              status = STATUS_BAD_DEVICE_TYPE;
              break;
  
          case IOCTL_AFD_LISTEN:
          {
              const struct afd_listen_params *params = in_buffer;
  
              TRACE( "backlog %u\n", params->backlog );
              if (out_size) FIXME( "unexpected output size %u\n", out_size );
              if (params->unknown1) FIXME( "listen: got unknown1 %#x\n", params->unknown1 );
              if (params->unknown2) FIXME( "listen: got unknown2 %#x\n", params->unknown2 );
  
              status = STATUS_BAD_DEVICE_TYPE;
              break;
          }
  
          case IOCTL_AFD_EVENT_SELECT:
          {
              const struct afd_event_select_params *params = in_buffer;
  
              TRACE( "event %p, mask %#x\n", params->event, params->mask );
              if (out_size) FIXME( "unexpected output size %u\n", out_size );
  
              status = STATUS_BAD_DEVICE_TYPE;
              break;
          }
  
          case IOCTL_AFD_GET_EVENTS:
              if (in_size) FIXME( "unexpected input size %u\n", in_size );
  
              status = STATUS_BAD_DEVICE_TYPE;
              break;
  
          case IOCTL_AFD_RECV:
          {
              const struct afd_recv_params *params = in_buffer;
              int unix_flags = 0;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (out_size) FIXME( "unexpected output size %u\n", out_size );
  
              if (in_size < sizeof(struct afd_recv_params))
              {
                  status = STATUS_INVALID_PARAMETER;
                  break;
              }
  
              if ((params->msg_flags & (AFD_MSG_NOT_OOB | AFD_MSG_OOB)) == 0 ||
                  (params->msg_flags & (AFD_MSG_NOT_OOB | AFD_MSG_OOB)) == (AFD_MSG_NOT_OOB | AFD_MSG_OOB))
              {
                  status = STATUS_INVALID_PARAMETER;
                  break;
              }
  
              if (params->msg_flags & ~(AFD_MSG_NOT_OOB | AFD_MSG_OOB | AFD_MSG_PEEK | AFD_MSG_WAITALL))
                  FIXME( "unknown msg_flags %#x\n", params->msg_flags );
              if (params->recv_flags & ~AFD_RECV_FORCE_ASYNC)
                  FIXME( "unknown recv_flags %#x\n", params->recv_flags );
  
              if (params->msg_flags & AFD_MSG_OOB)
                  unix_flags |= MSG_OOB;
              if (params->msg_flags & AFD_MSG_PEEK)
                  unix_flags |= MSG_PEEK;
              if (params->msg_flags & AFD_MSG_WAITALL)
                  FIXME( "MSG_WAITALL is not supported\n" );
  
              status = sock_recv( handle, event, apc, apc_user, io, fd, params->buffers, params->count, NULL,
                                  NULL, NULL, NULL, unix_flags, !!(params->recv_flags & AFD_RECV_FORCE_ASYNC) );
              break;
          }
  
          case IOCTL_AFD_WINE_RECVMSG:
          {
              struct afd_recvmsg_params *params = in_buffer;
              int unix_flags = 0;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (in_size < sizeof(*params))
              {
                  status = STATUS_BUFFER_TOO_SMALL;
                  break;
              }
  
              if (*params->ws_flags & WS_MSG_OOB)
                  unix_flags |= MSG_OOB;
              if (*params->ws_flags & WS_MSG_PEEK)
                  unix_flags |= MSG_PEEK;
              if (*params->ws_flags & WS_MSG_WAITALL)
                  FIXME( "MSG_WAITALL is not supported\n" );
  
              status = sock_recv( handle, event, apc, apc_user, io, fd, params->buffers, params->count, params->control,
                                  params->addr, params->addr_len, params->ws_flags, unix_flags, params->force_async );
              break;
          }
  
          case IOCTL_AFD_WINE_SENDMSG:
          {
              const struct afd_sendmsg_params *params = in_buffer;
              int unix_flags = 0;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (in_size < sizeof(*params))
              {
                  status = STATUS_BUFFER_TOO_SMALL;
                  break;
              }
  
              if (params->ws_flags & WS_MSG_OOB)
                  unix_flags |= MSG_OOB;
              if (params->ws_flags & WS_MSG_PARTIAL)
                  WARN( "ignoring MSG_PARTIAL\n" );
              if (params->ws_flags & ~(WS_MSG_OOB | WS_MSG_PARTIAL))
                  FIXME( "unknown flags %#x\n", params->ws_flags );
  
              status = sock_send( handle, event, apc, apc_user, io, fd, params->buffers, params->count,
                                  params->addr, params->addr_len, unix_flags, params->force_async );
              break;
          }
  
          case IOCTL_AFD_WINE_TRANSMIT:
          {
              const struct afd_transmit_params *params = in_buffer;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (in_size < sizeof(*params))
              {
                  status = STATUS_BUFFER_TOO_SMALL;
                  break;
              }
  
              status = sock_transmit( handle, event, apc, apc_user, io, fd, params );
              break;
          }
  
          case IOCTL_AFD_WINE_COMPLETE_ASYNC:
          {
              if (in_size != sizeof(NTSTATUS))
                  return STATUS_BUFFER_TOO_SMALL;
  
              status = *(NTSTATUS *)in_buffer;
              complete_async( handle, event, apc, apc_user, io, status, 0 );
              break;
          }
  
          case IOCTL_AFD_POLL:
              status = sock_poll( handle, event, apc, apc_user, io, in_buffer, in_size, out_buffer, out_size );
              break;
  
          case IOCTL_AFD_WINE_FIONREAD:
          {
              int value, ret;
  
              if (out_size < sizeof(int))
              {
                  status = STATUS_BUFFER_TOO_SMALL;
                  break;
              }
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
  #ifdef linux
              {
                  socklen_t len = sizeof(value);
  
                  /* FIONREAD on a listening socket always fails (see tcp(7)). */
                  if (!getsockopt( fd, SOL_SOCKET, SO_ACCEPTCONN, &value, &len ) && value)
                  {
                      *(int *)out_buffer = 0;
                      status = STATUS_SUCCESS;
                      complete_async( handle, event, apc, apc_user, io, status, 0 );
                      break;
                  }
              }
  #endif
  
              if ((ret = ioctl( fd, FIONREAD, &value )) < 0)
              {
                  status = sock_errno_to_status( errno );
                  break;
              }
              *(int *)out_buffer = value;
              status = STATUS_SUCCESS;
              complete_async( handle, event, apc, apc_user, io, status, 0 );
              break;
          }
  
          case IOCTL_AFD_WINE_SIOCATMARK:
          {
              int value, ret;
              socklen_t len = sizeof(value);
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (out_size < sizeof(int))
              {
                  status = STATUS_BUFFER_TOO_SMALL;
                  break;
              }
  
              if (getsockopt( fd, SOL_SOCKET, SO_OOBINLINE, &value, &len ) < 0)
              {
                  status = sock_errno_to_status( errno );
                  break;
              }
  
              if (value)
              {
                  *(int *)out_buffer = TRUE;
              }
              else
              {
                  if ((ret = ioctl( fd, SIOCATMARK, &value )) < 0)
                  {
                      status = sock_errno_to_status( errno );
                      break;
                  }
                  /* windows is reversed with respect to unix */
                  *(int *)out_buffer = !value;
              }
              status = STATUS_SUCCESS;
              complete_async( handle, event, apc, apc_user, io, status, 0 );
              break;
          }
  
          case IOCTL_AFD_WINE_GET_INTERFACE_LIST:
          {
  #ifdef HAVE_GETIFADDRS
              INTERFACE_INFO *info = out_buffer;
              struct ifaddrs *ifaddrs, *ifaddr;
              unsigned int count = 0;
              ULONG ret_size;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (getifaddrs( &ifaddrs ) < 0)
              {
                  status = sock_errno_to_status( errno );
                  break;
              }
  
              for (ifaddr = ifaddrs; ifaddr != NULL; ifaddr = ifaddr->ifa_next)
              {
                  if (ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_INET) ++count;
              }
  
              ret_size = count * sizeof(*info);
              if (out_size < ret_size)
              {
                  status = STATUS_PENDING;
                  complete_async( handle, event, apc, apc_user, io, STATUS_BUFFER_TOO_SMALL, 0 );
                  freeifaddrs( ifaddrs );
                  break;
              }
  
              memset( out_buffer, 0, ret_size );
  
              count = 0;
              for (ifaddr = ifaddrs; ifaddr != NULL; ifaddr = ifaddr->ifa_next)
              {
                  in_addr_t addr, mask;
  
                  if (!ifaddr->ifa_addr || ifaddr->ifa_addr->sa_family != AF_INET)
                      continue;
  
                  addr = ((const struct sockaddr_in *)ifaddr->ifa_addr)->sin_addr.s_addr;
                  mask = ((const struct sockaddr_in *)ifaddr->ifa_netmask)->sin_addr.s_addr;
  
                  info[count].iiFlags = 0;
                  if (ifaddr->ifa_flags & IFF_BROADCAST)
                      info[count].iiFlags |= WS_IFF_BROADCAST;
                  if (ifaddr->ifa_flags & IFF_LOOPBACK)
                      info[count].iiFlags |= WS_IFF_LOOPBACK;
                  if (ifaddr->ifa_flags & IFF_MULTICAST)
                      info[count].iiFlags |= WS_IFF_MULTICAST;
  #ifdef IFF_POINTTOPOINT
                  if (ifaddr->ifa_flags & IFF_POINTTOPOINT)
                      info[count].iiFlags |= WS_IFF_POINTTOPOINT;
  #endif
                  if (ifaddr->ifa_flags & IFF_UP)
                      info[count].iiFlags |= WS_IFF_UP;
  
                  info[count].iiAddress.AddressIn.sin_family = WS_AF_INET;
                  info[count].iiAddress.AddressIn.sin_port = 0;
                  info[count].iiAddress.AddressIn.sin_addr.WS_s_addr = addr;
  
                  info[count].iiNetmask.AddressIn.sin_family = WS_AF_INET;
                  info[count].iiNetmask.AddressIn.sin_port = 0;
                  info[count].iiNetmask.AddressIn.sin_addr.WS_s_addr = mask;
  
                  if (ifaddr->ifa_flags & IFF_BROADCAST)
                  {
                      info[count].iiBroadcastAddress.AddressIn.sin_family = WS_AF_INET;
                      info[count].iiBroadcastAddress.AddressIn.sin_port = 0;
                      info[count].iiBroadcastAddress.AddressIn.sin_addr.WS_s_addr = addr | ~mask;
                  }
  
                  ++count;
              }
  
              freeifaddrs( ifaddrs );
              status = STATUS_PENDING;
              complete_async( handle, event, apc, apc_user, io, STATUS_SUCCESS, ret_size );
  #else
              FIXME( "Interface list queries are currently not supported on this platform.\n" );
              status = STATUS_NOT_SUPPORTED;
  #endif
              break;
          }
  
          case IOCTL_AFD_WINE_KEEPALIVE_VALS:
          {
              struct tcp_keepalive *k = in_buffer;
              int keepalive;
  
              if (!in_buffer || in_size < sizeof(struct tcp_keepalive))
                  return STATUS_BUFFER_TOO_SMALL;
              keepalive = !!k->onoff;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (setsockopt( fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(int) ) < 0)
              {
                  status = STATUS_INVALID_PARAMETER;
                  break;
              }
  
              if (keepalive)
              {
  #ifdef TCP_KEEPIDLE
                  int idle = max( 1, (k->keepalivetime + 500) / 1000 );
  
                  if (setsockopt( fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(int) ) < 0)
                  {
                      status = STATUS_INVALID_PARAMETER;
                      break;
                  }
  #else
                  FIXME("ignoring keepalive timeout\n");
  #endif
              }
  
              if (keepalive)
              {
  #ifdef TCP_KEEPINTVL
                  int interval = max( 1, (k->keepaliveinterval + 500) / 1000 );
  
                  if (setsockopt( fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(int) ) < 0)
                      status = STATUS_INVALID_PARAMETER;
  #else
                  FIXME("ignoring keepalive interval\n");
  #endif
              }
  
              status = STATUS_SUCCESS;
              complete_async( handle, event, apc, apc_user, io, status, 0 );
              break;
          }
  
          case IOCTL_AFD_WINE_GETPEERNAME:
          {
              union unix_sockaddr unix_addr;
              socklen_t unix_len = sizeof(unix_addr);
              int len;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (getpeername( fd, &unix_addr.addr, &unix_len ) < 0)
              {
                  status = sock_errno_to_status( errno );
                  break;
              }
  
              len = sockaddr_from_unix( &unix_addr, out_buffer, out_size );
              if (out_size < len)
              {
                  status = STATUS_BUFFER_TOO_SMALL;
                  break;
              }
              io->Information = len;
              status = STATUS_SUCCESS;
              break;
          }
  
          case IOCTL_AFD_WINE_GET_SO_BROADCAST:
              return do_getsockopt( handle, io, SOL_SOCKET, SO_BROADCAST, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_SO_BROADCAST:
              return do_setsockopt( handle, io, SOL_SOCKET, SO_BROADCAST, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_SO_KEEPALIVE:
              return do_getsockopt( handle, io, SOL_SOCKET, SO_KEEPALIVE, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_SO_KEEPALIVE:
              return do_setsockopt( handle, io, SOL_SOCKET, SO_KEEPALIVE, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_SO_LINGER:
          {
              struct WS_linger *ws_linger = out_buffer;
              struct linger unix_linger;
              socklen_t len = sizeof(unix_linger);
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              ret = getsockopt( fd, SOL_SOCKET, SO_LINGER, &unix_linger, &len );
              if (needs_close) close( fd );
              if (!ret)
              {
                  ws_linger->l_onoff = unix_linger.l_onoff;
                  ws_linger->l_linger = unix_linger.l_linger;
                  io->Information = sizeof(*ws_linger);
              }
  
              return ret ? sock_errno_to_status( errno ) : STATUS_SUCCESS;
          }
  
          case IOCTL_AFD_WINE_SET_SO_LINGER:
          {
              const struct WS_linger *ws_linger = in_buffer;
              struct linger unix_linger;
  
              unix_linger.l_onoff = ws_linger->l_onoff;
              unix_linger.l_linger = ws_linger->l_linger;
  
              return do_setsockopt( handle, io, SOL_SOCKET, SO_LINGER, &unix_linger, sizeof(unix_linger) );
          }
  
          case IOCTL_AFD_WINE_GET_SO_OOBINLINE:
              return do_getsockopt( handle, io, SOL_SOCKET, SO_OOBINLINE, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_SO_OOBINLINE:
              return do_setsockopt( handle, io, SOL_SOCKET, SO_OOBINLINE, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_SO_REUSEADDR:
              return do_getsockopt( handle, io, SOL_SOCKET, SO_REUSEADDR, out_buffer, out_size );
  
          /* BSD socket SO_REUSEADDR is not 100% compatible to winsock semantics;
           * however, using it the BSD way fixes bug 8513 and seems to be what
           * most programmers assume, anyway */
          case IOCTL_AFD_WINE_SET_SO_REUSEADDR:
          {
              int fd, needs_close = FALSE;
              NTSTATUS status;
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              ret = setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, in_buffer, in_size );
  #ifdef __APPLE__
              if (!ret) ret = setsockopt( fd, SOL_SOCKET, SO_REUSEPORT, in_buffer, in_size );
  #endif
              if (needs_close) close( fd );
              return ret ? sock_errno_to_status( errno ) : STATUS_SUCCESS;
          }
  
          case IOCTL_AFD_WINE_SET_IP_ADD_MEMBERSHIP:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_ADD_MEMBERSHIP, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_SET_IP_ADD_SOURCE_MEMBERSHIP:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_SET_IP_BLOCK_SOURCE:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_BLOCK_SOURCE, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IP_DONTFRAGMENT:
          {
              socklen_t len = out_size;
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
  #ifdef IP_DONTFRAG
              ret = getsockopt( fd, IPPROTO_IP, IP_DONTFRAG, out_buffer, &len );
  #elif defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
              {
                  int value;
  
                  len = sizeof(value);
                  ret = getsockopt( fd, IPPROTO_IP, IP_MTU_DISCOVER, &value, &len );
                  if (!ret) *(DWORD *)out_buffer = (value != IP_PMTUDISC_DONT);
              }
  #else
              {
                  static int once;
  
                  if (!once++)
                      FIXME( "IP_DONTFRAGMENT is not supported on this platform\n" );
                  ret = 0; /* fake success */
              }
  #endif
              if (needs_close) close( fd );
              if (ret) return sock_errno_to_status( errno );
              io->Information = len;
              return STATUS_SUCCESS;
          }
  
          case IOCTL_AFD_WINE_SET_IP_DONTFRAGMENT:
  #ifdef IP_DONTFRAG
              return do_setsockopt( handle, io, IPPROTO_IP, IP_DONTFRAG, in_buffer, in_size );
  #elif defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) && defined(IP_PMTUDISC_DONT)
          {
              int value = *(DWORD *)in_buffer ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
  
              return do_setsockopt( handle, io, IPPROTO_IP, IP_MTU_DISCOVER, &value, sizeof(value) );
          }
  #else
          {
              static int once;
  
              if (!once++)
                  FIXME( "IP_DONTFRAGMENT is not supported on this platform\n" );
              return STATUS_SUCCESS; /* fake success */
          }
  #endif
  
          case IOCTL_AFD_WINE_SET_IP_DROP_MEMBERSHIP:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_DROP_MEMBERSHIP, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_SET_IP_DROP_SOURCE_MEMBERSHIP:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, in_buffer, in_size );
  
  #ifdef IP_HDRINCL
          case IOCTL_AFD_WINE_GET_IP_HDRINCL:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_HDRINCL, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_HDRINCL:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_HDRINCL, in_buffer, in_size );
  #endif
  
          case IOCTL_AFD_WINE_GET_IP_MULTICAST_IF:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_MULTICAST_IF, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_MULTICAST_IF:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_MULTICAST_IF, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IP_MULTICAST_LOOP:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_MULTICAST_LOOP, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_MULTICAST_LOOP:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_MULTICAST_LOOP, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IP_MULTICAST_TTL:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_MULTICAST_TTL, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_MULTICAST_TTL:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_MULTICAST_TTL, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IP_OPTIONS:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_OPTIONS, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_OPTIONS:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_OPTIONS, in_buffer, in_size );
  
  #ifdef IP_PKTINFO
          case IOCTL_AFD_WINE_GET_IP_PKTINFO:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_PKTINFO, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_PKTINFO:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_PKTINFO, in_buffer, in_size );
  #elif defined(IP_RECVDSTADDR)
          case IOCTL_AFD_WINE_GET_IP_PKTINFO:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_RECVDSTADDR, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_PKTINFO:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_RECVDSTADDR, in_buffer, in_size );
  #endif
  
          case IOCTL_AFD_WINE_GET_IP_TOS:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_TOS, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_TOS:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_TOS, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IP_TTL:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_TTL, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_TTL:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_TTL, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_SET_IP_UNBLOCK_SOURCE:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_UNBLOCK_SOURCE, in_buffer, in_size );
  
  #ifdef IP_UNICAST_IF
          case IOCTL_AFD_WINE_GET_IP_UNICAST_IF:
              return do_getsockopt( handle, io, IPPROTO_IP, IP_UNICAST_IF, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IP_UNICAST_IF:
              return do_setsockopt( handle, io, IPPROTO_IP, IP_UNICAST_IF, in_buffer, in_size );
  #endif
  
  #ifdef IPV6_ADD_MEMBERSHIP
          case IOCTL_AFD_WINE_SET_IPV6_ADD_MEMBERSHIP:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, in_buffer, in_size );
  #endif
  
          case IOCTL_AFD_WINE_GET_IPV6_DONTFRAG:
          {
              socklen_t len = out_size;
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
  #ifdef IPV6_DONTFRAG
              ret = getsockopt( fd, IPPROTO_IPV6, IPV6_DONTFRAG, out_buffer, &len );
  #elif defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DONT)
              {
                  int value;
  
                  len = sizeof(value);
                  ret = getsockopt( fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &value, &len );
                  if (!ret) *(DWORD *)out_buffer = (value != IPV6_PMTUDISC_DONT);
              }
  #else
              {
                  static int once;
  
                  if (!once++)
                      FIXME( "IPV6_DONTFRAGMENT is not supported on this platform\n" );
                  ret = 0; /* fake success */
              }
  #endif
              if (needs_close) close( fd );
              if (ret) return sock_errno_to_status( errno );
              io->Information = len;
              return STATUS_SUCCESS;
          }
  
          case IOCTL_AFD_WINE_SET_IPV6_DONTFRAG:
  #ifdef IPV6_DONTFRAG
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_DONTFRAG, in_buffer, in_size );
  #elif defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) && defined(IPV6_PMTUDISC_DONT)
          {
              int value = *(DWORD *)in_buffer ? IPV6_PMTUDISC_DO : IPV6_PMTUDISC_DONT;
  
              return do_setsockopt( handle, io, IPPROTO_IP, IPV6_MTU_DISCOVER, &value, sizeof(value) );
          }
  #else
          {
              static int once;
  
              if (!once++)
                  FIXME( "IPV6_DONTFRAGMENT is not supported on this platform\n" );
              return STATUS_SUCCESS; /* fake success */
          }
  #endif
  
  #ifdef IPV6_DROP_MEMBERSHIP
          case IOCTL_AFD_WINE_SET_IPV6_DROP_MEMBERSHIP:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, in_buffer, in_size );
  #endif
  
          case IOCTL_AFD_WINE_GET_IPV6_MULTICAST_HOPS:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_MULTICAST_HOPS:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IPV6_MULTICAST_IF:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_MULTICAST_IF, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_MULTICAST_IF:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_MULTICAST_IF, in_buffer, in_size );
  
          case IOCTL_AFD_WINE_GET_IPV6_MULTICAST_LOOP:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_MULTICAST_LOOP:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, in_buffer, in_size );
  
  #ifdef IPV6_RECVHOPLIMIT
          case IOCTL_AFD_WINE_GET_IPV6_RECVHOPLIMIT:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_RECVHOPLIMIT:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, in_buffer, in_size );
  #endif
  
  #ifdef IPV6_RECVPKTINFO
          case IOCTL_AFD_WINE_GET_IPV6_RECVPKTINFO:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_RECVPKTINFO, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_RECVPKTINFO:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_RECVPKTINFO, in_buffer, in_size );
  #endif
  
          case IOCTL_AFD_WINE_GET_IPV6_UNICAST_HOPS:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_UNICAST_HOPS, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_UNICAST_HOPS:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_UNICAST_HOPS, in_buffer, in_size );
  
  #ifdef IPV6_UNICAST_IF
          case IOCTL_AFD_WINE_GET_IPV6_UNICAST_IF:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_UNICAST_IF, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_UNICAST_IF:
              return do_setsockopt( handle, io, IPPROTO_IPV6, IPV6_UNICAST_IF, in_buffer, in_size );
  #endif
  
          case IOCTL_AFD_WINE_GET_IPV6_V6ONLY:
              return do_getsockopt( handle, io, IPPROTO_IPV6, IPV6_V6ONLY, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPV6_V6ONLY:
          {
              int fd, needs_close = FALSE;
              union unix_sockaddr addr;
              socklen_t len = sizeof(addr);
              NTSTATUS status;
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              if (!getsockname( fd, &addr.addr, &len ) && addr.addr.sa_family == AF_INET && !addr.in.sin_port)
              {
                  /* changing IPV6_V6ONLY succeeds on an unbound IPv4 socket */
                  WARN( "ignoring IPV6_V6ONLY on an unbound IPv4 socket\n" );
                  return STATUS_SUCCESS;
              }
  
              ret = setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, in_buffer, in_size );
              if (needs_close) close( fd );
              return ret ? sock_errno_to_status( errno ) : STATUS_SUCCESS;
          }
  
  #ifdef SOL_IPX
          case IOCTL_AFD_WINE_GET_IPX_PTYPE:
              return do_getsockopt( handle, io, SOL_IPX, IPX_TYPE, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_IPX_PTYPE:
              return do_setsockopt( handle, io, SOL_IPX, IPX_TYPE, in_buffer, in_size );
  #elif defined(SO_DEFAULT_HEADERS)
          case IOCTL_AFD_WINE_GET_IPX_PTYPE:
          {
              int fd, needs_close = FALSE;
              struct ipx value;
              socklen_t len = sizeof(value);
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              ret = getsockopt( fd, 0, SO_DEFAULT_HEADERS, &value, &len );
              if (needs_close) close( fd );
              if (ret) return sock_errno_to_status( errno );
              *(DWORD *)out_buffer = value.ipx_pt;
              return STATUS_SUCCESS;
          }
  
          case IOCTL_AFD_WINE_SET_IPX_PTYPE:
          {
              struct ipx value = {0};
  
              /* FIXME: should we retrieve SO_DEFAULT_HEADERS first and modify it? */
              value.ipx_pt = *(DWORD *)in_buffer;
              return do_setsockopt( handle, io, 0, SO_DEFAULT_HEADERS, &value, sizeof(value) );
          }
  #endif
  
  #ifdef HAS_IRDA
  #define MAX_IRDA_DEVICES 10
          case IOCTL_AFD_WINE_GET_IRLMP_ENUMDEVICES:
          {
              char buffer[offsetof( struct irda_device_list, dev[MAX_IRDA_DEVICES] )];
              struct irda_device_list *unix_list = (struct irda_device_list *)buffer;
              socklen_t len = sizeof(buffer);
              DEVICELIST *ws_list = out_buffer;
              int fd, needs_close = FALSE;
              NTSTATUS status;
              unsigned int i;
              int ret;
  
              if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL )))
                  return status;
  
              ret = getsockopt( fd, SOL_IRLMP, IRLMP_ENUMDEVICES, buffer, &len );
              if (needs_close) close( fd );
              if (ret) return sock_errno_to_status( errno );
  
              io->Information = offsetof( DEVICELIST, Device[unix_list->len] );
              if (out_size < io->Information)
                  return STATUS_BUFFER_TOO_SMALL;
  
              TRACE( "IRLMP_ENUMDEVICES: got %u devices:\n", unix_list->len );
              ws_list->numDevice = unix_list->len;
              for (i = 0; i < unix_list->len; ++i)
              {
                  const struct irda_device_info *unix_dev = &unix_list->dev[i];
                  IRDA_DEVICE_INFO *ws_dev = &ws_list->Device[i];
  
                  TRACE( "saddr %#08x, daddr %#08x, info %s, hints 0x%02x%02x\n",
                         unix_dev->saddr, unix_dev->daddr, unix_dev->info, unix_dev->hints[0], unix_dev->hints[1] );
                  memcpy( ws_dev->irdaDeviceID, &unix_dev->daddr, sizeof(unix_dev->daddr) );
                  memcpy( ws_dev->irdaDeviceName, unix_dev->info, sizeof(unix_dev->info) );
                  ws_dev->irdaDeviceHints1 = unix_dev->hints[0];
                  ws_dev->irdaDeviceHints2 = unix_dev->hints[1];
                  ws_dev->irdaCharSet = unix_dev->charset;
              }
              return STATUS_SUCCESS;
          }
  #endif
  
          case IOCTL_AFD_WINE_GET_TCP_NODELAY:
              return do_getsockopt( handle, io, IPPROTO_TCP, TCP_NODELAY, out_buffer, out_size );
  
          case IOCTL_AFD_WINE_SET_TCP_NODELAY:
              return do_setsockopt( handle, io, IPPROTO_TCP, TCP_NODELAY, in_buffer, in_size );
  
          default:
          {
              if ((code >> 16) == FILE_DEVICE_NETWORK)
              {
                  /* Wine-internal ioctl */
                  status = STATUS_BAD_DEVICE_TYPE;
              }
              else
              {
                  FIXME( "Unknown ioctl %#x (device %#x, access %#x, function %#x, method %#x)\n",
                         code, code >> 16, (code >> 14) & 3, (code >> 2) & 0xfff, code & 3 );
                  status = STATUS_INVALID_DEVICE_REQUEST;
              }
              break;
          }
      }
  
      if (needs_close) close( fd );
      return status;
  }
  