Signed-off-by: Zebediah Figura z.figura12@gmail.com --- dlls/ntdll/unix/socket.c | 310 ++++++++++++++++++++++++++++++++++++++- include/wine/afd.h | 11 ++ server/protocol.def | 11 ++ server/sock.c | 92 +++++++++++- 4 files changed, 410 insertions(+), 14 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 8d126e004f0..3d4b98e5871 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -89,6 +89,19 @@ static NTSTATUS wait_async( HANDLE handle, BOOL alertable ) return NtWaitForSingleObject( handle, alertable, NULL ); }
+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; @@ -101,6 +114,18 @@ struct async_recv_ioctl 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]; +}; + static NTSTATUS sock_errno_to_status( int err ) { switch (err) @@ -147,18 +172,108 @@ static NTSTATUS sock_errno_to_status( int err ) } }
-union unix_sockaddr +static socklen_t sockaddr_to_unix( const struct WS_sockaddr *wsaddr, int wsaddrlen, union unix_sockaddr *uaddr ) { - struct sockaddr addr; - struct sockaddr_in in; - struct sockaddr_in6 in6; + 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(struct WS_sockaddr_in6_old)) return 0; + if (wsaddrlen < sizeof(struct WS_sockaddr_in6)) + memcpy( &win, wsaddr, sizeof(struct WS_sockaddr_in6_old) ); + else + memcpy( &win, wsaddr, sizeof(struct WS_sockaddr_in6) ); + + 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 + if (wsaddrlen >= sizeof(struct WS_sockaddr_in6)) + uaddr->in6.sin6_scope_id = win.sin6_scope_id; +#endif + return sizeof(uaddr->in6); + } + #ifdef HAS_IPX - struct sockaddr_ipx 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 - struct sockaddr_irda 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->sir_lsap_sel = lsap_sel; + else + { + uaddr->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): + case sizeof(struct WS_sockaddr_in6_old): + 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 ) { @@ -650,6 +765,161 @@ static NTSTATUS sock_poll( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi 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, IO_STATUS_BLOCK *io, 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) + { + io->Status = status; + io->Information = 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; + + if (!NT_ERROR(status)) + { + io->Status = status; + io->Information = async->sent_len; + } + + SERVER_START_REQ( send_socket ) + { + req->status = status; + req->total = async->sent_len; + req->async = server_async( handle, &async->io, event, apc, apc_user, io ); + status = wine_server_call( req ); + wait_handle = wine_server_ptr_handle( reply->wait ); + options = reply->options; + } + 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; +}
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 ) @@ -741,6 +1011,32 @@ NTSTATUS sock_ioctl( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc 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_POLL: status = sock_poll( handle, event, apc, apc_user, io, in_buffer, in_size, out_buffer, out_size ); break; diff --git a/include/wine/afd.h b/include/wine/afd.h index 245aa525a7b..f003263bfc8 100644 --- a/include/wine/afd.h +++ b/include/wine/afd.h @@ -92,6 +92,7 @@ struct afd_poll_params #define IOCTL_AFD_WINE_CONNECT CTL_CODE(FILE_DEVICE_NETWORK, 203, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AFD_WINE_SHUTDOWN CTL_CODE(FILE_DEVICE_NETWORK, 204, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AFD_WINE_RECVMSG CTL_CODE(FILE_DEVICE_NETWORK, 205, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_AFD_WINE_SENDMSG CTL_CODE(FILE_DEVICE_NETWORK, 206, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_AFD_WINE_ADDRESS_LIST_CHANGE CTL_CODE(FILE_DEVICE_NETWORK, 323, METHOD_BUFFERED, FILE_ANY_ACCESS)
@@ -126,4 +127,14 @@ struct afd_recvmsg_params WSABUF *buffers; };
+struct afd_sendmsg_params +{ + const struct WS(sockaddr) *addr; + unsigned int addr_len; + unsigned int ws_flags; + int force_async; + unsigned int count; + const WSABUF *buffers; +}; + #endif diff --git a/server/protocol.def b/server/protocol.def index 88e5ad6a96b..93e2f56e037 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1502,6 +1502,17 @@ struct poll_socket_output @END
+/* Perform a send on a socket */ +@REQ(send_socket) + async_data_t async; /* async I/O parameters */ + unsigned int status; /* status of initial call */ + unsigned int total; /* number of bytes already sent */ +@REPLY + obj_handle_t wait; /* handle to wait on for blocking send */ + unsigned int options; /* device open options */ +@END + + /* Retrieve the next pending console ioctl request */ @REQ(get_next_console_request) obj_handle_t handle; /* console server handle */ diff --git a/server/sock.c b/server/sock.c index 04e27328d80..e521818dc4b 100644 --- a/server/sock.c +++ b/server/sock.c @@ -768,14 +768,11 @@ static int sock_dispatch_asyncs( struct sock *sock, int event, int error ) event &= ~(POLLIN | POLLPRI); }
- if (is_fd_overlapped( sock->fd )) + if (event & POLLOUT && async_waiting( &sock->write_q )) { - if (event & POLLOUT && async_waiting( &sock->write_q )) - { - if (debug_level) fprintf( stderr, "activating write queue for socket %p\n", sock ); - async_wake_up( &sock->write_q, STATUS_ALERTED ); - event &= ~POLLOUT; - } + if (debug_level) fprintf( stderr, "activating write queue for socket %p\n", sock ); + async_wake_up( &sock->write_q, STATUS_ALERTED ); + event &= ~POLLOUT; }
if (event & (POLLERR | POLLHUP)) @@ -2508,3 +2505,84 @@ DECL_HANDLER(poll_socket)
release_object( sock ); } + +DECL_HANDLER(send_socket) +{ + struct sock *sock = (struct sock *)get_handle_obj( current->process, req->async.handle, 0, &sock_ops ); + unsigned int status = req->status; + timeout_t timeout = 0; + struct async *async; + struct fd *fd; + + if (!sock) return; + fd = sock->fd; + + if (status != STATUS_SUCCESS) + { + /* send() calls only clear and reselect events if unsuccessful. */ + sock->pending_events &= ~FD_WRITE; + sock->reported_events &= ~FD_WRITE; + } + + /* If we had a short write and the socket is nonblocking (and the client is + * not trying to force the operation to be asynchronous), return success. + * Windows actually refuses to send any data in this case, and returns + * EWOULDBLOCK, but we have no way of doing that. */ + if (status == STATUS_DEVICE_NOT_READY && req->total && (sock->state & FD_WINE_NONBLOCKING)) + status = STATUS_SUCCESS; + + /* send() returned EWOULDBLOCK or a short write, i.e. cannot send all data yet */ + if (status == STATUS_DEVICE_NOT_READY && !(sock->state & FD_WINE_NONBLOCKING)) + { +#ifdef SO_SNDTIMEO + struct timeval tv; + socklen_t len = sizeof(tv); + + /* Set a timeout on the async if necessary. + * + * We want to do this *only* if the client gave us STATUS_DEVICE_NOT_READY. + * If the client gave us STATUS_PENDING, it expects the async to always + * block (it was triggered by WSASend*() with a valid OVERLAPPED + * structure) and for the timeout not to be respected. */ + if (is_fd_overlapped( fd ) && !getsockopt( get_unix_fd( fd ), SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, &len )) + timeout = tv.tv_sec * -10000000 + tv.tv_usec * -10; +#endif + + status = STATUS_PENDING; + } + + /* are we shut down? */ + if (status == STATUS_PENDING && !(sock->state & FD_WRITE)) status = STATUS_PIPE_DISCONNECTED; + + if ((async = create_request_async( fd, get_fd_comp_flags( fd ), &req->async ))) + { + int success = 0; + + if (status == STATUS_SUCCESS) + { + struct iosb *iosb = async_get_iosb( async ); + iosb->result = req->total; + release_object( iosb ); + success = 1; + } + else if (status == STATUS_PENDING) + { + success = 1; + } + set_error( status ); + + if (timeout) + async_set_timeout( async, timeout, STATUS_IO_TIMEOUT ); + + if (status == STATUS_PENDING) + queue_async( &sock->write_q, async ); + + /* always reselect; we changed reported_events above */ + sock_reselect( sock ); + + reply->wait = async_handoff( async, success, NULL, 0 ); + reply->options = get_fd_options( fd ); + release_object( async ); + } + release_object( sock ); +}