diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index 1894772d23..6d25881d2f 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -266,6 +266,19 @@ typedef struct ws2_async struct iovec iovec[1]; } ws2_async; +typedef struct ws2_accept_async +{ + HANDLE listen_socket; + HANDLE accept_socket; + LPOVERLAPPED user_overlapped; + ULONG_PTR cvalue; + PVOID buf; /* buffer to write data to */ + int data_len; + int local_len; + int remote_len; + struct ws2_async *read; +} ws2_accept_async; + /****************************************************************/ /* ----------------------------------- internal data */ @@ -316,6 +329,8 @@ static struct WS_servent *WS_dup_se(const struct servent* p_se); int WSAIOCTL_GetInterfaceCount(void); int WSAIOCTL_GetInterfaceName(int intNumber, char *intName); +static void WS_AddCompletion( SOCKET sock, ULONG_PTR CompletionValue, NTSTATUS CompletionStatus, ULONG Information ); + #define MAP_OPTION(opt) { WS_##opt, opt } static const int ws_sock_map[][2] = @@ -1489,6 +1504,122 @@ static NTSTATUS WS2_async_recv( void* user, IO_STATUS_BLOCK* iosb, NTSTATUS stat return status; } +/* user APC called upon async accept completion */ +static void WINAPI ws2_async_accept_apc( void *arg, IO_STATUS_BLOCK *iosb, ULONG reserved ) +{ + struct ws2_accept_async *wsa = arg; + + HeapFree( GetProcessHeap(), 0, wsa->read ); + HeapFree( GetProcessHeap(), 0, wsa ); +} + +/*********************************************************************** + * WS2_async_accept_recv (INTERNAL) + * + * This function is used to finish the read part of an accept request. It is + * needed to place the completion on the correct socket (listener). + */ +static NTSTATUS WS2_async_accept_recv( void *arg, IO_STATUS_BLOCK *iosb, NTSTATUS status, void **apc ) +{ + void *junk; + struct ws2_accept_async *wsa = arg; + + status = WS2_async_recv( wsa->read, iosb, status, &junk ); + if (status == STATUS_PENDING) + return status; + + if (wsa->user_overlapped->hEvent) + SetEvent(wsa->user_overlapped->hEvent); + if (wsa->cvalue) + WS_AddCompletion( HANDLE2SOCKET(wsa->listen_socket), wsa->cvalue, iosb->u.Status, iosb->Information ); + + *apc = ws2_async_accept_apc; + return status; +} + +/*********************************************************************** + * WS2_async_accept (INTERNAL) + * + * This is the function called to satisfy the AcceptEx callback + */ +static NTSTATUS WS2_async_accept( void *arg, IO_STATUS_BLOCK *iosb, NTSTATUS status, void **apc ) +{ + struct ws2_accept_async *wsa = arg; + int len; + char *addr; + + TRACE("status: 0x%x listen: %p, accept: %p\n", status, wsa->listen_socket, wsa->accept_socket); + + if (status == STATUS_ALERTED) + { + SERVER_START_REQ( accept_into_socket ) + { + req->lhandle = wine_server_obj_handle( wsa->listen_socket ); + req->ahandle = wine_server_obj_handle( wsa->accept_socket ); + status = wine_server_call( req ); + } + SERVER_END_REQ; + + if (status == STATUS_CANT_WAIT) + return STATUS_PENDING; + + if (status == STATUS_INVALID_HANDLE) + { + FIXME("AcceptEx accepting socket closed but request was not cancelled"); + status = STATUS_CANCELLED; + } + } + else if (status == STATUS_HANDLES_CLOSED) + status = STATUS_CANCELLED; /* strange windows behavior */ + + if (status != STATUS_SUCCESS) + goto finish; + + /* WS2 Spec says size param is extra 16 bytes long...what do we put in it? */ + addr = ((char *)wsa->buf) + wsa->data_len; + len = wsa->local_len - sizeof(int); + WS_getpeername(HANDLE2SOCKET(wsa->accept_socket), + (struct WS_sockaddr *)(addr + sizeof(int)), &len); + *(int *)addr = len; + + addr += wsa->local_len; + len = wsa->remote_len - sizeof(int); + WS_getsockname(HANDLE2SOCKET(wsa->accept_socket), + (struct WS_sockaddr *)(addr + sizeof(int)), &len); + *(int *)addr = len; + + if (!wsa->read) + goto finish; + + SERVER_START_REQ( register_async ) + { + req->type = ASYNC_TYPE_READ; + req->async.handle = wine_server_obj_handle( wsa->accept_socket ); + req->async.callback = wine_server_client_ptr( WS2_async_accept_recv ); + req->async.iosb = wine_server_client_ptr( iosb ); + req->async.arg = wine_server_client_ptr( wsa ); + status = wine_server_call( req ); + } + SERVER_END_REQ; + + if (status != STATUS_PENDING) + goto finish; + + return STATUS_SUCCESS; + +finish: + iosb->u.Status = status; + iosb->Information = 0; + + if (wsa->user_overlapped->hEvent) + SetEvent(wsa->user_overlapped->hEvent); + if (wsa->cvalue) + WS_AddCompletion( HANDLE2SOCKET(wsa->listen_socket), wsa->cvalue, iosb->u.Status, iosb->Information ); + + *apc = ws2_async_accept_apc; + return status; +} + /*********************************************************************** * WS2_send (INTERNAL) * @@ -1712,6 +1843,126 @@ SOCKET WINAPI WS_accept(SOCKET s, struct WS_sockaddr *addr, return INVALID_SOCKET; } +/*********************************************************************** + * AcceptEx + */ +BOOL WINAPI WS2_AcceptEx(SOCKET listener, SOCKET acceptor, PVOID dest, DWORD dest_len, + DWORD local_addr_len, DWORD rem_addr_len, LPDWORD received, + LPOVERLAPPED overlapped) +{ + DWORD status; + struct ws2_accept_async *wsa; + int fd; + ULONG_PTR cvalue = (overlapped && ((ULONG_PTR)overlapped->hEvent & 1) == 0) ? (ULONG_PTR)overlapped : 0; + + TRACE("(%lx, %lx, %p, %d, %d, %d, %p, %p)\n", listener, acceptor, dest, dest_len, local_addr_len, + rem_addr_len, received, overlapped); + + if (!dest) + { + SetLastError(WSAEINVAL); + return FALSE; + } + + if (!overlapped) + { + SetLastError(WSA_INVALID_PARAMETER); + return FALSE; + } + + fd = get_sock_fd( listener, FILE_READ_DATA, NULL ); + if (fd == -1) + { + SetLastError(WSAENOTSOCK); + return FALSE; + } + release_sock_fd( listener, fd ); + + fd = get_sock_fd( acceptor, FILE_READ_DATA, NULL ); + if (fd == -1) + { + SetLastError(WSAEINVAL); + return FALSE; + } + release_sock_fd( acceptor, fd ); + + wsa = HeapAlloc( GetProcessHeap(), 0, sizeof(*wsa) ); + if(!wsa) + { + SetLastError(WSAEFAULT); + return FALSE; + } + + wsa->listen_socket = SOCKET2HANDLE(listener); + wsa->accept_socket = SOCKET2HANDLE(acceptor); + wsa->user_overlapped = overlapped; + wsa->cvalue = cvalue; + wsa->buf = dest; + wsa->data_len = dest_len; + wsa->local_len = local_addr_len; + wsa->remote_len = rem_addr_len; + wsa->read = NULL; + + if (wsa->data_len) + { + /* set up a read request if we need it */ + wsa->read = HeapAlloc( GetProcessHeap(), 0, FIELD_OFFSET(struct ws2_async, iovec[1]) ); + if (!wsa->read) + { + HeapFree( GetProcessHeap(), 0, wsa ); + SetLastError(WSAEFAULT); + return FALSE; + } + + wsa->read->hSocket = wsa->accept_socket; + wsa->read->flags = 0; + wsa->read->addr = NULL; + wsa->read->addrlen.ptr = NULL; + wsa->read->n_iovecs = 1; + wsa->read->first_iovec = 0; + wsa->read->iovec[0].iov_base = wsa->buf; + wsa->read->iovec[0].iov_len = wsa->data_len; + } + + SERVER_START_REQ( register_async ) + { + req->type = ASYNC_TYPE_READ; + req->async.handle = wine_server_obj_handle( SOCKET2HANDLE(listener) ); + req->async.callback = wine_server_client_ptr( WS2_async_accept ); + req->async.iosb = wine_server_client_ptr( overlapped ); + req->async.arg = wine_server_client_ptr( wsa ); + /* We don't set event or completion since we may also have to read */ + status = wine_server_call( req ); + } + SERVER_END_REQ; + + if(status != STATUS_PENDING) HeapFree( GetProcessHeap(), 0, wsa ); + + SetLastError( NtStatusToWSAError(status) ); + return FALSE; +} + +/*********************************************************************** + * GetAcceptExSockaddrs + */ +void WINAPI WS2_GetAcceptExSockaddrs(PVOID buffer, DWORD data_size, DWORD local_size, DWORD remote_size, + struct WS_sockaddr **local_addr, LPINT local_addr_len, + struct WS_sockaddr **remote_addr, LPINT remote_addr_len) +{ + char *cbuf = buffer; + TRACE("(%p, %d, %d, %d, %p, %p, %p, %p)\n", buffer, data_size, local_size, remote_size, local_addr, + local_addr_len, remote_addr, remote_addr_len ); + cbuf += data_size; + + *local_addr_len = *(int *) cbuf; + *local_addr = (struct WS_sockaddr *)(cbuf + sizeof(int)); + + cbuf += local_size; + + *remote_addr_len = *(int *) cbuf; + *remote_addr = (struct WS_sockaddr *)(cbuf + sizeof(int)); +} + /*********************************************************************** * bind (WS2_32.2) */ @@ -2856,11 +3107,13 @@ INT WINAPI WSAIoctl(SOCKET s, } else if ( IsEqualGUID(&acceptex_guid, lpvInBuffer) ) { - FIXME("SIO_GET_EXTENSION_FUNCTION_POINTER: unimplemented AcceptEx\n"); + *(LPFN_ACCEPTEX *)lpbOutBuffer = WS2_AcceptEx; + return 0; } else if ( IsEqualGUID(&getaccepexsockaddrs_guid, lpvInBuffer) ) { - FIXME("SIO_GET_EXTENSION_FUNCTION_POINTER: unimplemented GetAcceptExSockaddrs\n"); + *(LPFN_GETACCEPTEXSOCKADDRS *)lpbOutBuffer = WS2_GetAcceptExSockaddrs; + return 0; } else if ( IsEqualGUID(&transmitfile_guid, lpvInBuffer) ) { diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index b2a22afd57..87403c6baa 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -3839,12 +3839,12 @@ static void test_AcceptEx(void) bret = pAcceptEx(listener, acceptor, buffer, 0, 0, sizeof(struct sockaddr_in) + 16, &bytesReturned, &overlapped); - ok(bret == FALSE && WSAGetLastError() == WSAEINVAL, "AcceptEx on too small local address size " + todo_wine ok(bret == FALSE && WSAGetLastError() == WSAEINVAL, "AcceptEx on too small local address size " "returned %d + errno %d\n", bret, WSAGetLastError()); bret = pAcceptEx(listener, acceptor, buffer, 0, sizeof(struct sockaddr_in) + 16, 0, &bytesReturned, &overlapped); - ok(bret == FALSE && WSAGetLastError() == WSAEINVAL, "AcceptEx on too small remote address size " + todo_wine ok(bret == FALSE && WSAGetLastError() == WSAEINVAL, "AcceptEx on too small remote address size " "returned %d + errno %d\n", bret, WSAGetLastError()); bret = pAcceptEx(listener, acceptor, buffer, 0, @@ -3856,7 +3856,7 @@ static void test_AcceptEx(void) bret = pAcceptEx(listener, acceptor, buffer, sizeof(buffer) - 2*(sizeof(struct sockaddr_in) + 16), sizeof(struct sockaddr_in) + 16, sizeof(struct sockaddr_in) + 16, &bytesReturned, &overlapped); - ok(bret == FALSE && WSAGetLastError() == WSAEINVAL, "AcceptEx on a non-listening socket " + todo_wine ok(bret == FALSE && WSAGetLastError() == WSAEINVAL, "AcceptEx on a non-listening socket " "returned %d + errno %d\n", bret, WSAGetLastError()); iret = listen(listener, 5); @@ -3879,13 +3879,14 @@ static void test_AcceptEx(void) bret = pAcceptEx(listener, acceptor, buffer, 0, sizeof(struct sockaddr_in) + 16, sizeof(struct sockaddr_in) + 16, &bytesReturned, &overlapped); - ok((bret == FALSE && WSAGetLastError() == WSAEINVAL) || broken(bret == FALSE && WSAGetLastError() == ERROR_IO_PENDING) /* NT4 */, + todo_wine ok((bret == FALSE && WSAGetLastError() == WSAEINVAL) || broken(bret == FALSE && WSAGetLastError() == ERROR_IO_PENDING) /* NT4 */, "AcceptEx on already pending socket returned %d + errno %d\n", bret, WSAGetLastError()); if (bret == FALSE && WSAGetLastError() == ERROR_IO_PENDING) { /* We need to cancel this call, otherwise things fail */ bret = CancelIo((HANDLE) listener); ok(bret, "Failed to cancel failed test. Bailing...\n"); if (!bret) return; + WaitForSingleObject(overlapped.hEvent, 0); bret = pAcceptEx(listener, acceptor, buffer, 0, sizeof(struct sockaddr_in) + 16, sizeof(struct sockaddr_in) + 16, @@ -3894,7 +3895,7 @@ static void test_AcceptEx(void) } iret = connect(acceptor, (struct sockaddr*)&bindAddress, sizeof(bindAddress)); - ok((iret == SOCKET_ERROR && WSAGetLastError() == WSAEINVAL) || broken(!iret) /* NT4 */, + todo_wine ok((iret == SOCKET_ERROR && WSAGetLastError() == WSAEINVAL) || broken(!iret) /* NT4 */, "connecting to acceptex acceptor succeeded? return %d + errno %d\n", iret, WSAGetLastError()); if (!iret || (iret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)) { /* We need to cancel this call, otherwise things fail */ @@ -4132,7 +4133,7 @@ static void test_AcceptEx(void) acceptor = INVALID_SOCKET; dwret = WaitForSingleObject(overlapped.hEvent, 1000); - ok(dwret == WAIT_OBJECT_0 || broken(dwret == WAIT_TIMEOUT) /* NT4/2000 */, + todo_wine ok(dwret == WAIT_OBJECT_0 || broken(dwret == WAIT_TIMEOUT) /* NT4/2000 */, "Waiting for accept event failed with %d + errno %d\n", dwret, GetLastError()); if (dwret != WAIT_TIMEOUT) { @@ -4143,6 +4144,7 @@ static void test_AcceptEx(void) bret = CancelIo((HANDLE) listener); ok(bret, "Failed to cancel failed test. Bailing...\n"); if (!bret) return; + WaitForSingleObject(overlapped.hEvent, 0); } acceptor = socket(AF_INET, SOCK_STREAM, 0);